Codex vs Claude

Debugging Together: You Hold the Hypothesis, It Does the Volume

David Guzenburg/ / 11 min read

A human adds three log statements because thirty is tedious. That tedium is what decides which bugs you find.

diagnosticsobservabilityworkflowtesting

Debugging is the one task that resists handing off

Almost everything else in an agent workflow improves when you can write a done condition before starting. Debugging is defined by not being able to: you do not know what "fixed" looks like until you know what is broken, and finding that out is the work.

So the asynchronous model does not apply, and the pairing model applies awkwardly, because the thing being paired on is not implementation but inference. What works is a specific division of labour, and getting it wrong produces the most recognisable failure in agent-assisted engineering: a confident explanation that is wrong, followed by a fix for a problem you do not have.

The division that works

You hold the hypothesis. The agent does the instrumentation.

That is the whole arrangement. Deciding what is suspicious, what to rule out next, and when an explanation is too convenient — those are judgement under uncertainty, informed by knowing how this system has failed before. Adding logging to fourteen call sites, writing the script that correlates two log streams by request identifier, bisecting forty commits, dumping state at a breakpoint and summarising it — those are volume, and volume is what an agent is for.

The mistake is asking the agent to do the first job because it will willingly produce an answer, and the answer will be plausible.

Instrumentation at volume is the real capability

Underrated because it is unglamorous. A human debugging a hard problem adds three log statements, because adding thirty is tedious and most of them will be useless. That tedium constrains the investigation: you instrument where you already suspect, which means you find what you expected and miss what you did not.

An agent will add thirty and remove them afterwards without complaint. That changes what is practical to look at, and it is genuinely why some bugs get found faster — not because the reasoning was better but because the search was wider.

Add a trace point at every exit from OrderService — every return,
every throw, every early return in a guard clause. Each should log:

  {"at": "<file>:<line>", "order": orderId, "state": order.status,
   "elapsed_ms": Date.now() - t0}

to a single file, one JSON object per line. Do not change any logic.
Do not add try/catch. Do not "improve" anything you see along the way.

Then run the failing case and give me the sequence of trace points.

The prohibitions are load-bearing. An agent given an open instruction while reading suspicious code will fix things, and a fix applied mid-investigation destroys the reproduction you were about to study.

Say "do not fix anything yet"

The single most useful sentence in a debugging session. Left to itself, an agent will move from observation to remedy in one step, because that is the shape of most of its training and because the code it is reading genuinely does contain improvable things.

The cost is that you lose the failing state. Once the code has changed, you no longer know whether the behaviour you were investigating is still reproducible, and if the change accidentally masked it you will spend an hour looking for a bug that is now hidden rather than absent.

Make it deterministic before you make it understood

The highest-value early step in any hard bug is converting "sometimes" into "always", and it is a good task for an agent because it is mechanical search.

Pin the seed. Freeze the clock. Force the concurrency to one, then to two. Replay the exact request. Remove the network. Each of these either preserves the failure — which narrows the cause — or removes it, which narrows it more. An agent will work through that matrix systematically in a way people usually do not, because people jump to the variable they suspect.

The intermittent bug is not the hard part

Once a failure reproduces on demand, most bugs become ordinary. Spend the effort there first rather than reasoning about a behaviour you can only observe by waiting for it.

Bisecting is nearly free now

A bisect over sixty commits with a manual reproduction is an afternoon nobody has. With a scripted check it is a command, and the agent's job is writing the check — a script that exits zero on good and non-zero on bad, reliably, at every commit in the range including the ones where the build was different.

That last clause is where the effort goes, and it is exactly the fiddly mechanical work worth delegating. The result is an answer of the form "this commit introduced it", which converts an open question into a diff.

Correlating across services

The other volume task. When a failure spans several components, the useful artefact is a single ordered timeline built from several log sources, keyed by whatever identifier survives the boundaries.

Assembling that is scripting: parse three formats, normalise timestamps across time zones, join on a request identifier that is called something different in each system, sort, and print. It is an hour of fiddly work for a person and a few minutes for an agent, and the timeline is frequently where the answer becomes obvious without anyone reasoning about it.

The plausible explanation is the trap

Here is the failure to guard against most carefully. Asked what is causing a bug, an agent will produce an explanation. It will be coherent, it will reference real code, and it will account for the symptom. It may still be wrong, and nothing in its presentation distinguishes the two cases.

The defence is to insist on mechanism rather than cause, and then to verify. "Show me the line where the value becomes null, and the log entry proving it" is falsifiable. "The race condition in the cache layer is causing this" is a story. The first can be checked in a minute; the second gets believed and acted on.

Ask for what would disprove it

A stronger version, and a habit worth building. Having heard an explanation, ask what observation would show it to be false, then go and make that observation.

This does two things. It converts a narrative into a prediction, which is the only form in which it can be wrong. And it frequently reveals that the explanation makes no distinguishing prediction at all — that it is compatible with everything you have seen and therefore explains nothing, which is a category of answer that is very easy to accept when it is written fluently.

When the agent is confidently wrong

It will happen, and the recovery is worth having a plan for. The pattern is that the wrong explanation persists: having proposed it, the agent interprets subsequent evidence in its light, and you get increasingly elaborate accounts of why the data is consistent with the original theory.

Once you notice that, do not argue it out of the position. Start a new session with the evidence and without the theory. The evidence is portable — logs, timeline, bisect result — and the theory is the thing you want to leave behind.

The instrumentation has to come out

Thirty trace points are wonderful during an investigation and must not survive it. The failure is a repository that accumulates debug logging from six past incidents, which is noise in production and confusion for the next person reading the code.

The discipline: instrumentation goes in on its own commit, so it comes out with a single revert. Not scattered through the fix, which is how it stays. Some of it may deserve to become permanent structured logging, and that should be a deliberate choice made afterwards rather than the residue of forgetting to clean up.

Turn the session into a test before you close it

The reproduction you built is worth more than the fix. It is a failing case that captures a real defect, which is the most expensive kind of test to write and the one you now have for free.

Converting it takes a few minutes and it is the step that gets skipped because the bug is fixed and the urgency has gone. Skipping it means the next occurrence of the same class of bug starts from nothing again.

Sometimes stop and think alone

A last honest note. The interaction is fast, and fast interaction is seductive during a hard problem: there is always another thing to check, and checking feels like progress in exactly the way described in long-horizon runs.

Hard bugs are frequently solved by sitting back and reconsidering the premise, and that does not happen while you are in a fast loop with something that will always suggest another experiment. When an investigation has been going for an hour with no narrowing, the right move is often to stop, write down what you know, and think about it away from the terminal.

The reproduction is worth more than the explanation

Worth stating as its own principle. In a session that has gone on too long, the thing to protect is the ability to make the bug happen on demand. That is the asset: it survives a wrong theory, it survives a restarted session, it is transferable to a colleague, and it becomes a test at the end.

Explanations are cheap and disposable by comparison. Several will be wrong, one will be right, and none of them matter without something to check them against. If you have to choose where to spend the first hour of a hard bug, spend it on the reproduction.

Watch what happens to your own understanding

A quieter cost worth naming. Debugging is how people learn systems — the map you build during a nasty investigation is what makes you fast in that code for years. Delegating the investigation delegates the learning.

That is fine for a subsystem you will never touch again and a real loss in code you own. The division proposed here happens to protect against it: if you are holding the hypothesis, you are the one building the model, and the agent is doing the part that was never teaching you anything anyway.

Logs an agent will read are logs a person can read

A useful side effect of working this way. Instrumentation designed for an agent to summarise — one structured object per line, a consistent identifier, an explicit location field — is also exactly what makes logs searchable and joinable for people.

Several teams have improved their production logging as a byproduct of debugging with an agent, because the format that makes machine summarisation possible is the format that was always recommended and never quite worth the effort. That is a genuine incidental gain, and it is worth consolidating rather than reverting once the bug is closed.

The last practical note: keep the evidence in a file rather than in the conversation. A hard bug outlives a session, and a session that ends takes its scrollback with it. A directory with the timeline, the bisect output, the trace log and a short note on what has been ruled out is portable between sessions, shareable with a colleague, and the thing you will want at three in the afternoon when the theory you have been building all morning turns out to be wrong.

Write down what you have ruled out

A small discipline that pays off in every long investigation. Keep a running list of eliminated hypotheses with the evidence that eliminated each. Not in the conversation — in a file, so it survives a restarted session.

Its value is that it stops you re-examining the same theory at hour three, which is otherwise almost guaranteed, and it makes the investigation transferable: a colleague picking it up gets the state of knowledge rather than a summary of activity. It is also the artefact that makes a failed investigation useful, because "here is what it is not" is a genuine result.

Takeaway

Debugging resists handoff because you cannot write the done condition until you know the answer. The division that works is that you hold the hypothesis and the agent does volume — thirty trace points instead of three, a scripted bisect, a timeline joined across three log formats. Say "do not fix anything yet" explicitly, make the failure deterministic before trying to understand it, and demand mechanism with evidence rather than a plausible cause: ask what observation would disprove the explanation, then go and make it.

Keep reading
Codex vs Claude

Screenshot-Driven UI Debugging: The Picture Is Evidence, Not a Diagnosis

Vision input catches overflow, alignment and contrast, and misses every bug whose cause is a rule. Pairing the capture with computed styles up the ancestor chain, keeping captures deterministic, and closing the loop with an after image.

Codex vs Claude

Handing Off a Task: Write the Check Before You Write the Brief

What decides whether work can go to an unsupervised agent is not model capability but whether done is machine-checkable. The three task shapes that hand off well, task-specific assertions, and designing for failure as a valid outcome.

Workflow Architecture

Test-Driven Loops: Giving an Agent a Definition of Done

Why TDD fits agent workflows, who should own each step of the cycle, the instruction that makes it runnable, and why test speed is the binding constraint.

Codex vs Claude

Pairing With an Agent: Granularity Is the Whole Game

A check-in is valuable before a commitment and a tax during implementation. Where the real decision points are, the sentence that fixes ambiguous cases, and why the most useful conversation usually happens at the end.

← Database Access: You Wanted the Schema and You Granted the Rows  ·  Hundreds of Workers or a Few Focused Subagents? →

All codex vs claude articles  ·  Every article