July 28, 2026
8 minute read
Why your triage agent proposes the wrong fix
A ticket says checkout failed. The click, the failed request, the slow query, the changed row, and the commit sit in five systems that share no key. Why that join has to be stamped as it happens.
A ticket arrives and says checkout failed. Your triage agent reads it, reads your repository, and proposes a change to the checkout handler. The change is coherent. It is also a guess, and the reason is not that the model is weak.
The evidence that would settle the question already exists inside systems you already pay for. It is scattered across five of them, and none of them shares a key with the others.
One event, five records
Walk the failure in order. The person clicked Pay, and that click is a record in whatever interaction tool you run. The request the click produced returned a 500, and that is an issue in your error tracker. The request was slow because one query took nine seconds, and that is a line in your logging tool. The query left a row in a state the code did not expect, and that is in your database. The change that made the query behave that way is a commit.
Five systems, five records, one event. Ask any of them what the other four saw and you get nothing back, because the join column does not exist. There is no field in the error tracker that names the click, and no field in the click that names the query.
A person can bridge it. A model cannot.
An experienced engineer bridges this gap constantly and barely notices doing it. Five tabs. Timestamps lined up by eye. The same customer recognized by email address in two systems and by an internal identifier in the other three. Two requests discarded because they turned out to belong to somebody else. Twenty minutes later there is a story, and it works because a person can hold an ambiguous match, weigh it, and decide it is good enough.
A coding agent has none of that. It cannot open your logging tool, it holds no database credentials, and even handed all five feeds it has no rule for deciding which log line belongs to which click. So it falls back to the one input it can read completely, which is the ticket. Everything it says after that is downstream of a sentence somebody typed from memory.
Why the join cannot be added afterwards
The reasonable next thought is to build the join later. Take the timestamp, take the user identifier, and match. Teams try this, and the result is the worst available outcome: mostly right.
Concurrency. The same person has two tabs open, or one screen fires three requests inside the same second. Timestamp plus user identifier returns several candidates and nothing distinguishes them.
Clock skew. The browser clock, the server clock, and the database clock disagree by tens or hundreds of milliseconds, which is wider than the window you are trying to match inside.
Retries. One click produces four requests. Matching on proximity in time tends to pick whichever attempt landed nearest the click, which is often the one that eventually succeeded.
Fan out. One request becomes six spans across three services and two queue hops. That chain is only reconstructable if something carried an identifier through it at the time.
Missing identity. A good share of the interesting failures happen before login, or to a session that never resolved to a known user at all.
Each of those is survivable alone. Together they mean a retroactive join is a probability estimate wearing the clothes of a fact. When it is right, nothing happens and nobody learns anything. When it is wrong, an agent reasons confidently about a request that belonged to a different customer, and that output is exactly as fluent and well cited as the correct one.
Stamping the key while it happens
The alternative is to establish the link at the moment of failure, which means the decision has to be made before the ticket exists. Crumbtrail does this with W3C trace context, which is a published standard rather than anything of ours.
The browser SDK opens a trace when the interaction starts and puts a traceparent header on the requests that interaction produces. Your backend already understands that header if it runs OpenTelemetry, so the span it creates inherits the same trace identifier. Express middleware correlates the incoming request back to the click that caused it, including across origins. Row changes recorded during that request land in the same session as db.diff events.
terminal
# keep the instrumentation you already have
npm i crumbtrail-node
npx crumbtrail-server serve --port 9898 --output ./.crumbtrail/sessions
# point your existing exporter at the capture server
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:9898Two settings carry the whole mechanism. Set crumbtrail.session.id as a resource attribute and spans file into the session that owns them. Propagate traceparent from the browser and a click in the interface links to the backend error through one trace identifier. Nothing else has to agree on anything.
Row level database diffs are recorded across Postgres, MySQL, MSSQL, and SQLite, so what the request did to your data is part of the same object rather than something you reconstruct later from a backup and a hunch.
What the agent reads instead
With the key stamped, the five records are already one object by the time anyone knows they will be needed. The agent asks for the latest issue, gets a ranked summary, and walks from the session manifest into a specific piece of evidence and then into the raw events around the failure only if it needs them. It is reading what happened rather than a description of what happened.
The change in the answer is rarely subtle. Mostly it is that the agent stops proposing a patch to code that was never the problem. Bad data, a config value that drifted, and an infrastructure fault all present to a reporter as checkout failed, and a model holding only ticket text will write a patch for all three, because a patch is the thing it knows how to write.
Try it on a failure you already lost time to
The mechanism ships in the MIT licensed packages on npm, and the trial needs no card. Run the wizard from your repository root, reproduce a bug you already understand, and read the correlated bundle next to what your agent said from the ticket alone.
terminal
npx crumbtrail