Engineering notesView as Markdown ↗

When an agent files a bug against working code

An agent's bug report is evidence about what the agent saw, not about what happened. When the observation came through your own instrumentation, the instrument is a suspect. Confirm against a source your code does not write before acting on the diagnosis — especially when the suggested fix points at code that is load-bearing.

A bug arrived from an agent: sending an invoice reaches only one of the contacts flagged to receive it, not all of them. Severity high. It named a plausible cause — somewhere in the recipient resolver an array was being reduced to its first element — and suggested fixing exactly that.

The report was well-written, specific, and reproducible. It was also wrong in the way that matters most: it pointed at working code.

The observation was real. The diagnosis wasn't.

Step four of the agent's own reproduction was "call the delivery-status tool". That is where the evidence came from, and that tool was part of the system under suspicion.

It resolved a single message id from the entity record, then filtered the whole event timeline by it. Fine for a send to one person — but the send loop dispatches one message per recipient, each with its own id, and stamps the record with the first. So the filter kept one recipient's events and discarded the rest. Asked about that invoice it returned two of the eight events that existed, both belonging to the same person.

The agent saw one recipient because the instrument showed one recipient. Its conclusion followed correctly from its evidence. The evidence was the problem.

What settled it

Delivery events are written by the email provider's webhook — a record of what happened on the wire, created by a party that does not consult our beliefs about what we sent.

Reading those directly showed both contacts receiving the invoice, both confirmed delivered, on every send, under separate message ids. The fan-out had always worked. Nobody had missed an invoice.

If your application wrote the evidence, it can only tell you what your application believed. Independent confirmation has to come from outside your own write path.

That is the triage move worth keeping. Provider webhooks, payment processor records, third-party audit trails — anything your code did not author — is what turns "the tool says X" into "X happened".

The cost of taking the suggestion on trust

The suggested fix was to loosen the recipient resolver.

That resolver de-duplicates recipients by email address, deliberately: two contacts sharing one mailbox must receive one copy of an invoice, not two. Loosening it would have started double-sending to every client whose contacts share an inbox.

So the proposed fix would have introduced a genuine defect in order to resolve an imaginary one — strictly worse than doing nothing, and it would have shipped with a confident bug report as its justification.

A report can be entirely right about the symptom and entirely wrong about the cause. The symptom earns investigation. The cause earns verification.

What was actually broken, because something usually is

Re-diagnosis rarely means "no defect". It usually means "not that one".

A bounce is recorded against the bouncing recipient's message id — which, on a fan-out, is usually not the stamped one. So the tool reported delivered from the first recipient's record while another contact's address was dead. Nobody could see it.

The summary now takes the worst outcome across all recipients, evaluated ahead of the entity's own status flag. A failure on any recipient can no longer hide behind someone else's delivery. That hole was closed before a live bounce found it.

The parity half of this story — two implementations of one question, drifting apart — is covered separately in two places, one fact. The dashboard had been answering correctly the whole time, keyed on the entity rather than a message id. The agent-facing tool was the one that had drifted.

Make the response answer the question

The deeper fix was not the query. It was that a flat list of eight events never answered the question anyone was actually asking, which is "did all of them get it?"

The response now carries a per-recipient roll-up — each address, its own message id, its latest event, and a plain reached boolean — plus counts, plus a field stating whether the answer is complete or partial. Ambiguous output invites confident wrong conclusions, and an agent will produce them faster than a human will.

Triage checklist for an agent-filed report

  1. Separate observation from diagnosis. Both are in the report; only one is evidence.
  2. Ask what produced the observation. If it was your own instrumentation, the instrument is a suspect.
  3. Confirm against a source you do not write. Provider records, processor logs, anything external.
  4. Verify the defect exists where the report says before changing that code.
  5. Check what the suggested fix would break. Load-bearing behaviour often looks like a bug from outside.
  6. Fix the instrument too. Otherwise the next agent files the same report.

Related

Last updated 2026-08-20