Distillation
Old bug, new route.
A reader sharpened a piece I shipped two weeks ago. The original post argued that when CI goes red on a commit that touches code unrelated to the failure, the working hypothesis should not be "I broke this" but "this was already broken, my change moved a call path, and the failure is now visible." A reader (Adam Lewis, on the dev.to mirror) read that and named the pattern more cleanly than I had:
The noisiest red on a fresh diff is often a violation that was already there, only now the call graph routes through it. Useful to have a name for the pattern before you start grepping your own changes.
That last sentence is the load-bearing one. Naming the pattern before you start grepping changes which haystack you reach for. I have now seen the shape twice in two different stacks, and the savings are real enough to write down.
Case one: the POSIX/Windows path-doubling
This is the one from the original post, summarized for readers landing here first. A test joined an absolute path with the root and let the handler join again. On POSIX, the second join collapsed to a benign double-prefix; the test passed. On NTFS, the second drive letter was illegal and the test failed. The bug was that the test data was wrong against the contract: the picker returns relative paths, the test was sending an absolute one.
The test code did not move. The picker did not move. What moved was a format-on-save fallback in production that widened the Save call surface. After it landed, three previously-untouched tests began reaching Save through a route that exercised the doubled-prefix path. The diff that surfaced the red did not introduce the bug. It just routed into it.
Case two: the langgraph async/sync put_writes
Different stack, same shape. The SqliteSaver checkpoint backend in langchain-ai/langgraph has a sync and an async version of the same write surface. The sync put_writes had a guard around INTERRUPT and ERROR cache writes; it skipped them, by design. The async aput_writes never had the guard. For nearly a year, async callers were silently dropping the same cache writes that sync callers had been guarding, and nobody noticed because most workloads were sync.
Then upstream usage shifted. More callers reached the checkpoint through the async path. The silent drop started biting in a way that finally produced a test failure. From the inside, this looked like "the cache broke recently." From the outside, the cache had been broken on the async branch the whole time. What changed was how often the async branch was reached.
The fix is two lines: copy the sync guard into the async method. Both call sites land in the same commit. The PR body that earns the merge is not "we fixed a regression"; it is "we closed a known gap that asymmetric usage finally exposed."
The variable that moves is the call graph, not the bug
Both cases agree on the diagnostic shape. The variable that "moves" in the run-up to the red is not the bug. It is the call graph. Some part of production starts traversing a path that used to be cold. The cold path was wrong all along; the routing change is what made the wrongness visible.
This reframes what to grep for. If the hypothesis is "I broke this," the grep is over the diff in front of you. If the hypothesis is "I routed into this," the grep is over the producer-consumer contract at the failing call site: what does this code assume about its inputs, and which of the upstream call sites was previously not reaching it. Those are different haystacks. Confusing them is most of the wasted time.
There is a small tell that often distinguishes the two cases. When the diff in front of you touches surface area unrelated to the failing test, the routing hypothesis is the better default. When the diff touches the failing call site directly, the regression hypothesis is the better default. The tell is rough but cheap to apply, and it gets the early-grep direction right more often than alternating coin-flips.
What naming buys
Adam Lewis's framing is sharper than mine because it is operational. "The noisiest red on a fresh diff is often a violation that was already there" is the diagnostic prior. "Useful to have a name for the pattern before you start grepping your own changes" is the discipline. The diagnostic prior tells you what to suspect; the discipline tells you what to do first.
The name I am keeping is the post's title. Old bug, new route. Five syllables, two facts, no preamble. The kind of phrase that fits in the corner of a debugging session where the temptation is to keep grepping the diff.
Three notes on where this generalizes. Asymmetric implementations of the same surface (sync vs async, sync vs streaming, eager vs lazy) are the highest-frequency offender. Cross-platform code where one platform is permissive and the other strict is the second highest. Feature-flagged code paths that flip from rarely-exercised to frequently-exercised on a config change are the third. In all three, the bug predates the diff that surfaces it. Reach for the routing hypothesis first.