A user reported a crash on the export route: a null delivery date reached the date formatter and threw. The agent added a null check at that one call site, fed the reported payload into the test, saw it pass, and closed the ticket cleanly. Three days later the report route threw the exact same exception. That route carried a near-identical copied block, and the guard had never been placed there, because the patch targeted the occurrence the user named rather than the pattern that produced it.
The agent had fixed the line in the stack trace. It had not fixed the condition that repeated on the next copy.
Why the reported site becomes the only target
Given a reported error, the agent's shortest path is to locate the line in the trace, patch it, and re-run the failing case. Search tools reward a literal match on the error message or the stack frame, so the loop converges on that one call site instead of asking what broader invariant the call site violated.
Copy-pasted code makes this worse. A block written once and duplicated across routes keeps the same missing guard in every copy. The agent finds the copy that crashed, patches it, and never enumerates the rest, because the task frame names a symptom location, not a failure class.
The test reinforces the trap. The regression fixture reproduces the reported payload on the reported route; it passes; the suite reports green. There is no assertion that the sibling construction behaves the same way, so green reads as solved.
What a local fix leaves behind
- Sibling copies keep the defect. Every duplicated block that builds the same unguarded value still throws when its own edge case arrives.
- The guard special-cases one input. Checking a single call site protects that route, but leaves the underlying value able to reach a formatter null on any other path.
- Tests cover only the reported case. The fixture mirrors the user's payload; the untouched copies exercise a shape the test never models.
- The issue returns under a new ticket. The same exception filed from a different route looks like a fresh report instead of a known, ungeneralized fix.
- The root cause stays unnamed. A commit that describes the one route never records the invariant, so the next agent has to rediscover the pattern from scratch.
The tell is a completion summary that names the route or function the user quoted, with no mention of where else that construct appears.
Generalize the pattern before you close
A patch is complete when the condition cannot reach any call site, not when the reported one passes:
- Find every site that builds the same value. Search for the pattern that produces the unguarded input, not just the literal line from the trace; copied routes and shared helpers are the likely homes of the next crash.
- Name the invariant instead of the location. State what must hold before the formatter runs, then enforce that invariant at the boundary where the value is built, so every consumer inherits the guard.
- Replicate the failing shape on the siblings. Run the null or edge input through each duplicated path before declaring the fix done; a guard that exists on one copy does not protect the others.
- Let the commit describe the failure class. A message that says only "fix export date crash" leaves the recurrence undocumented; naming the invariant gives the next pass a place to start.
- Ask who else constructs this value. Before closing, enumerate callers and entry points that reach the same code, and treat an unpatched sibling as open work rather than out of scope.
An occurrence and a pattern are different sizes of problem
Fixing the reported call site removes the crash the user saw. It does not remove the condition that crashes elsewhere. Until every copy that builds the same value is checked, a green regression test means the reported case passed, not that the defect was gone.
Top comments (0)