We fixed the same bug three times before we found it.

A fresh Farstax workspace could not save a private GitHub repository correctly. The symptom was clear enough: repository setup failed during the authenticated clone path.

The first explanation made sense.

We changed the code.

It still failed.

The second explanation made sense too.

We changed the code again.

Still broken.

By the third repair, we had built a small tower of sensible changes around a diagnosis that was not actually true.

The fourth attempt started differently. Instead of asking what the code appeared to be missing, we reproduced what the failing child process could actually see.

That changed everything.

The first three fixes were not random

This was not a sequence of careless guesses.

The repository credential helper needed the control-plane URL so it could obtain a short-lived repository credential. We could see an environment file being staged and promoted around the clone operation. The failing path looked very much like a question of which file existed at which moment.

So one fix made the helper source another environment file.

When that file was not yet in its final location during a fresh clone, the next fix staged it earlier and passed the temporary path into the operation.

When the staged file turned out not to be readable by the runtime user, the next fix corrected its permissions.

Each step followed logically from the previous model of the failure.

That was the problem.

We were getting better at making the wrong model internally consistent.

The real failure was smaller

The useful production-shaped test ended up being almost embarrassingly simple.

The correct environment file already contained the control-plane URL. The helper was already sourcing that file.

But sourcing a shell file does not automatically mean a value becomes part of the environment inherited by a child process.

The helper then started a Node process. That child looked for the URL in its environment and saw nothing.

The missing operation was an export.

One line.

The previous three changes had moved files, staged files and changed permissions around a value that had been in the right file all along.

Once we reproduced the actual shell-to-child boundary, the earlier fixes became obviously unnecessary and were removed.

Agents make a plausible hypothesis dangerously productive

This is the part that feels different when agents are writing a lot of the code.

A plausible diagnosis used to have friction attached to it. Someone had to understand enough of the area to make the change, wire the test, run the suite and prepare the patch. That time often created opportunities for doubt.

A capable coding agent can compress most of that into minutes.

That is useful when the hypothesis is right.

When it is wrong, it means the wrong idea can acquire implementation, regression coverage and a neat explanation with very little resistance.

The agent has not done anything uniquely foolish. Humans follow plausible explanations too. The difference is throughput.

We can now turn a mistaken mental model into respectable-looking software faster than before.

A test can preserve the hypothesis instead of testing it

The earlier fixes had tests.

That did not save us.

Some of the assertions proved things like which file the generated helper sourced, whether a temporary path was supplied and whether permissions were set before the clone.

Those were real properties of the proposed fix.

They were not the original failure.

This distinction is easy to lose once a test goes red and then green. The colour change feels wonderfully objective.

But a test is only strong evidence if the thing it disagrees about is close enough to the thing that actually broke.

If I hypothesise that a missing file caused the failure, write a test saying the file should exist earlier, then make it exist earlier, I have proved my implementation satisfies my hypothesis.

I have not proved the hypothesis explains production.

The fourth test asked a better question

The eventual regression started a real shell with a real temporary environment file and then started a child Node process from that shell.

The assertion was not ‘does the generated script contain the line we expect?’

It was effectively ‘can the child process actually see this value?’

Without the export, no.

With it, yes.

That is a much smaller test than reproducing the whole workspace provisioning journey. It is also much closer to the boundary where reality was disagreeing with us.

This is the kind of reduction I want more of: not a bigger integration harness by default, but the smallest executable reproduction that preserves the important failure.

Fast implementation changes where the scarce work lives

I used to think the expensive part of debugging was making and checking the fix.

Increasingly, that is not true.

The expensive part is earning the diagnosis.

What exactly failed? Which process knew what? Which observation distinguishes one explanation from another? What would still fail if our current story were wrong?

Once those questions have good answers, implementation can be remarkably quick.

Without them, quick implementation just lets us explore the wrong branch of the tree faster.

This is not an argument for slowing the agents down

The obvious reaction would be more approval gates. Make the agent explain itself. Require a larger plan. Add another review before it touches the code.

I do not think that would have fixed this particular mistake.

The explanations were plausible. A longer explanation could have been plausible for more paragraphs.

What we needed was disagreement from reality sooner.

Reproduce the symptom. Reduce it to the boundary that matters. Then let the agent move quickly.

The lesson from four fixes was not ‘do less with agents’.

It was almost the opposite.

If changing software is becoming cheap, we can afford to spend more of our attention on making sure the thing we are changing is the thing that is actually broken.

The agent can implement a hypothesis very quickly.

Reality still has to approve the hypothesis.