Debugging
/debugFind the cause before choosing the fix.
Preserve the evidence
Understand the failure before you change it
A test goes red. A request returns a server error. The tempting response is to open the file named in the error and start changing lines. People do this under pressure, and an AI agent can produce plausible patches even faster when its loop rewards visible action. Neither speed nor plausibility demonstrates a cause.
Treat the first error as evidence. A stack trace, the list of function calls that led to a failure, may point directly to the defect. It may instead show where bad data was finally rejected. Record the input, environment, logs and exact failing behaviour before editing. Otherwise a change can hide the symptom while leaving the cause in place, and the original evidence becomes harder to recover.
Collapse the search space with evidence
Each diagnostic step should remove plausible causes without destroying the failure you need to study.
- Preserve the symptom Capture exact input, environment, output and timing.
- Reproduce reliably Turn a report into a controlled failing case.
- Bisect one boundary Compare good and bad state, input, version or path.
- Trace the causal chain Show how the candidate cause produces the observed failure.
- Compare remedies Choose by consequence, reversibility and proof.
A smaller search space is useful only if the true cause remains inside it.
Collapse the search space
Demonstrate one cause, then compare the remedies
An unlocated bug invites many plausible edits. A demonstrated cause narrows the problem, but it does not imply exactly one fix. You may reject invalid input, repair it, change the downstream assumption or redesign the boundary that admitted it. The debugging job has two stages: establish a causal chain with evidence, then evaluate remedy branches against the product's intended behaviour and risk.
First, reproduce the failure: create the smallest repeatable setup that makes the same thing go wrong. A failing automated test is ideal because it preserves the input and expected behaviour, which is why writing the test first helps. If the failure is intermittent, capture traces and increase the conditions that trigger it rather than pretending it is deterministic.
Then bisect. Bisection means dividing the possible causes into two groups, running the same reproduction and keeping the half that still fails. You can split a change history, an input file, a pipeline or a feature set. Repeat until one boundary or assumption explains the behaviour. The aim is not to delete half the code. It is to ask a sequence of questions whose answers steadily reduce uncertainty.
A complete evidence trail
From a failing upload to a defensible change
Suppose a CSV upload returns a server error for one customer file. Preserve that file and turn the request into a failing test. The stack trace ends at a database constraint, but valid uploads use the same database path. Split the file in half and rerun. Only the second half fails. Repeat until one trailing row remains. Every field in that row is empty. Tracing the quantity value shows the importer converts the blank string into an invalid number, then sends it to a column that accepts only whole numbers.
Now there is one demonstrated causal chain and several remedies. Defaulting the blank to zero preserves the row but invents a quantity. Rejecting every blank row is safer but turns a harmless trailing line into a failed upload. Ignoring a completely empty trailing row while rejecting a partially filled row matches the file contract more closely. Choose that branch, add tests for both cases, run the original reproduction and then run the wider importer suite. The original file now uploads without writing an empty record; a partially populated row returns a clear validation error without writing partial data. The evidence supports the cause; product rules and risk determine the repair.
An agent is useful here when the loop around it preserves the failing case, requires a cause statement before edits and limits each experiment to one controlled variable. The model remains one variable too. A different model may search or reason better, but no model makes an unexplained patch into evidence. After the focused test turns green, prove the wider claim with regression tests, logs and review.
A fix can hide the cause
A plausible patch and a causal diagnosis are different products.
Guess-and-patch loop
- Recognise a familiar symptom Choose the first plausible cause.
- Change several things The result becomes hard to attribute.
- Symptom disappears No proof the failure cannot return elsewhere.
replace familiarity with causality
Find-and-prove loop
- Reproduce one failure Hold the symptom stable.
- Change one causal variable Predict how the observation should move.
- Prove remedy and boundary Run the original case and an adjacent non-regression case.
The shortest trustworthy fix is the one whose causal chain and evidence can be explained.
A practical debugging contract
Separate diagnosis, remedy and proof
Use a short contract for human and AI debugging. Preserve the failure. Reproduce it or gather enough traces to characterise it. Bisect the search space. State the causal chain and the evidence for it. List plausible remedies and their trade-offs. Change one controlled variable for each experiment. Finally, rerun the original case, nearby edge cases and the relevant wider checks.
During an active security, safety, data-loss or availability incident, containment may come first. Preserve the available evidence, then use a reversible rollback, feature disablement or traffic block to limit harm. That is a temporary control, not a cause-based repair.
The method also bends when failures depend on timing, distributed systems, hardware or unavailable production state. Evidence may be statistical rather than one red test, and several causes may interact. The principle still holds: reduce uncertainty before increasing permanent change. Debugging is the discipline of earning confidence in why a system failed and why a chosen remedy should hold.