# Gather evidence before changing code

Canonical page: https://yishto.com/en/learn/developer-workflow/debugging-with-evidence/

Author: [Yishto](https://yishto.com/en/about/)

Diagnose an indefinite search spinner with reproducible inputs, network evidence, breakpoints, and a synthetic trace timeline, then verify the repair and neighboring failure paths.

Published: 2026-11-10
Updated: 2026-11-10

A document-search page remains in its loading state. The console shows no exception, while the network panel shows a completed request. Changing timeout values may not help: the response shape, state transition, or an older request might be responsible. Diagnosis starts by locating an observable failure and using evidence to test explanations.

The examples use synthetic responses and timestamps, without a monitoring service. The result should be a record that another developer can reproduce, assess, and use to verify the repair.

## Specify the reproduction

Record the actions, input, expected result, actual result, and environment. For example: search for a missing title; expect an empty-result message; observe an indefinite spinner. State whether it happens consistently, whether refresh changes it, and the last known good version. Retain input and version information sufficient for reproduction.

Separate launch failures, HTTP results, data-contract errors, and interface-state errors. These failures can occur in sequence, but an HTTP success does not establish feature success. A valid response displayed incorrectly belongs to another boundary from a failed network request.

Reduce the case to one query, the necessary synthetic data, and one interaction path. Avoid upgrading dependencies, changing caching, and rewriting components together. Even if the symptom disappears, several simultaneous changes leave its cause unclear.

## Read network and log evidence precisely

The browser Network panel exposes the request address, method, status, response, and timing phases. Confirm that the request happened, then inspect its contents. HTTP 200 can accompany an application error field. Successful JSON parsing does not establish correct field types. Compare the response with its contract.

A console stack identifies where an exception surfaced, which may only be the first use of bad data. Nearby log timestamps do not establish causality. Correlate an operation with a request identifier and record necessary information at the service entry, dependency call, and response-mapping boundaries.

Headers, cookies, bodies, and HAR exports may contain credentials or personal data. Redact and reduce evidence before sharing it. Methods, paths, statuses, types, error codes, and synthetic identifiers usually answer the diagnostic question without a complete real user payload.

## Use breakpoints to test a hypothesis

A breakpoint exposes the current scope, stack, and local values. Step into a function when its internals are relevant; step over when the question concerns values before and after the call. This applies to debugger workflows such as VS Code and Python pdb.

Inspect the actual data type when the response arrives, then check the state-update branch. If only nonempty results select success, an empty array may omit the transition that ends loading. Express that as a falsifiable hypothesis: repeated empty arrays should fail while nonempty arrays should follow the other path.

Pausing changes timing. A race disappearing under a debugger is not evidence of race freedom. Heavy logging can alter timing too. Supplement inspection with unpaused repeated runs and deliberately controlled response order.

## Check the timeline's measurement boundaries

OpenTelemetry traces and spans represent related operations. A span generally describes a work interval with attributes and relationships. Cross-service tracing requires context propagation, and sampling affects what is retained. An absent trace can reflect missing instrumentation or propagation rather than an absent request.

The example assumes one time basis and arbitrary units for request, query, and render. It checks nonnegative durations and that the modeled child intervals fit within the request. It does not use an OpenTelemetry SDK or test clock synchronization.

```python
spans = [
    {"name": "request", "start": 0, "end": 50},
    {"name": "query", "start": 8, "end": 30},
    {"name": "render", "start": 32, "end": 45},
]
assert all(s["end"] >= s["start"] for s in spans)
assert spans[1]["end"] - spans[1]["start"] == 22
assert all(0 <= s["start"] <= s["end"] <= 50 for s in spans)
print({s["name"]: s["end"]-s["start"] for s in spans})
```

The output is `{'request': 50, 'query': 22, 'render': 13}`. Query duration is 30 minus 8. The two child durations total 35 rather than the request's 50; other intervals remain unmodeled. Overlapping spans would make summation double-count time, so it cannot automatically represent total latency.

The checks apply only to these supplied intervals. Real instrumentation must distinguish clock purposes and cover appropriate boundaries. Missing queue or serialization spans do not justify blaming unexplained time on the database.

## Repair the established cause

Once evidence establishes the missing empty-result branch, implement the loading-to-empty transition. Retain the diff and rerun the original reproduction, then test populated results, network failures, malformed data, and consecutive queries. A repair that introduces a neighboring failure remains incomplete.

For intermittent behavior, record the repetitions and controlled conditions. Logs establish observations; assertions establish behavior for their inputs. Neither establishes every future request's safety. Describe remaining uncertainty as a concrete path to test.

Remove temporary full-object logging after diagnosis. Retain useful structured state information and check that public errors do not expose internal stacks. Verify that the remaining logs can still locate the same class of failure.

Retain a recoverable baseline before repair. When investigating database output, inspect query inputs, result counts, and serialization before editing stored data to remove the symptom. Separate observations from untested explanations so another developer can continue the experiment.

## Exercise: produce a falsifiable diagnosis

Create empty, populated, and invalid synthetic search responses. Record the expected state for each, isolate the smallest failing input, and propose two competing explanations. Design an observation that supports or rejects each explanation before choosing one repair.

A reviewer must reproduce the original error, inspect the behavioral difference, and observe a clear terminal state for all three responses. Complete two searches in reverse order and verify that an old result cannot replace the new one. Extend the timeline checks to detect negative durations and children outside the parent interval, while stating that these checks do not validate a real tracing system or distributed clocks.

## Sources & further reading

- [Debugger configuration](https://code.visualstudio.com/docs/debugtest/debugging)
- [Python debugger](https://docs.python.org/3/library/pdb.html)
- [OpenTelemetry concepts](https://opentelemetry.io/docs/concepts/)

Sources checked on 2026-11-10. Examples are teaching data, not production measurements.

## Related guides

- [Make every release verifiable and recoverable](https://yishto.com/en/learn/delivery/cicd-monitoring-and-incidents/)
- [Observe AI apps: first-token latency, total time, and cost](https://yishto.com/en/learn/production/latency-cost-observability/)
