Why is our agent confidently wrong right after a tool call failed?
The tool returned HTTP 200 with an error message in the body, or an empty result set. The agent treated the absence of data as evidence and missed the failure. Nothing in the response forced a distinction between "nothing matched" and "something broke."
Also known as swallowed error, false-success tool response
What does this look like in production?
- The agent states something as fact ("there are no open orders") immediately downstream of a tool call that actually errored
- Logs show a 200-status response with an error string embedded in the body, treated by the agent as normal data
- An empty list comes back from a tool and the agent proceeds as if that confirms absence rather than possible failure
- Per-tool error rates in your metrics are near zero while user-reported wrongness for the same tool is not
Why does it happen?
Many internal tools and APIs always return a 200 with a JSON body. They signal a problem with an error field or message inside the payload. The calling code is never forced to branch on a non-2xx status. An agent that reads the body as data has no structural reason to check for that field. A capable model will synthesize a confident statement from whatever shape of data it received. This can include an error message it misreads as content. It can include an empty array it reads as "confirmed nothing exists" when the call never ran.
The result looks identical to a correct, grounded answer from the outside. It is fluent, specific, and delivered without hedging. The model has no idea anything went wrong upstream. It is not lying. It is accurately reporting what it was told, and what it was told was itself a masked failure.
How do you confirm it's this?
- Correlate per-tool error rate (from logs, not from agent behavior) against downstream task success. A tool with silent errors will show low logged errors but low correlated success
- Assert in your eval suiteAn eval suite is a versioned set of test cases that measures whether outputs are good. that every tool result carries an explicit, checkable status field, and flag any tool that doesn't
- Force a tool to fail in a test run. Check whether the final answer of the agent reflects that failure or treats the result as normal
- Sample raw tool responses for anything containing "error," "failed," or similar strings inside an otherwise-200 body
How do you fix it?
- Return typed, discriminated ok/error results
Every tool response should carry an explicit status that the calling code and the model can both branch on. Do not bury an error string in a body shaped like success.
- Make empty results explicit
Make "0 rows matched" and "the query failed to run" distinguishable results. Distinguish them in your code and in what you feed back to the model. Never collapse them into the same empty array.
- Fail the step instead of continuing on ambiguous data
When a tool result is ambiguous or malformed, stop that step and surface the failure. Do not let the agent proceed and paper over it with a fluent guess.
- Add an eval where the tool is forced to fail
Include test cases where a dependency is deliberately broken. Assert that the agent says so and does not produce a plausible answer anyway.
What this doesn't cover
This applies when the tool itself masks failure as success. If the tool reports the error correctly and the agent still proceeds confidently, the defect is in the agent error handling. The tool response shape is fine.
Sources
- Model Context Protocol - MCP Specification: Tools (2026-07-28)
The specification text our tool-design and catalogue rules are checked against. Retrieved 2026-08-24.
- JSON Schema - JSON Schema, Draft 2020-12
The schema specification a tool description must satisfy under our rule bank. Retrieved 2026-08-24.
A Ship Audit runs this full checklist against your actual system and hands back a written, prioritized plan.