Skip to main content

Your Error Messages Are Prompts Now: Writing Failure Output for AI Agents

· 10 min read
Tian Pan
Software Engineer

Count the readers of your stack traces. For most internal tools, the answer used to be "one tired engineer, occasionally." Today the highest-volume reader of your error output is almost certainly a language model inside a retry loop. Coding agents read your linter warnings, your CLI usage strings, your API error bodies, and your test failures thousands of times a day — far more often than any human ever will. And unlike the human, the agent takes every word literally.

That changes what an error message is. It is no longer documentation of a failure. It is an instruction injected into the context window of the next attempt — a prompt you wrote months ago, now steering fleets of agents you've never met. A precise error converges the loop in one retry. A vague or misleading one sends the agent spiraling: wrong fixes, --no-verify workarounds, hallucinated flags, burned tokens. If you maintain a tool, a service, or a build system, you are already doing prompt engineering. You're just doing it in your error strings, and probably by accident.

The Retry Loop Is a Conversation You Didn't Know You Were Having

When a human hits an error, the error message is one input among many. They have a mental model of the system, they can ask a coworker, they can shrug and Google it. The message only has to be good enough to point a knowledgeable reader in the right direction. Decades of terrible error messages survived on exactly this forgiveness.

An agent has none of those fallbacks. When a tool call fails, the error body is frequently the only new information entering the loop. The agent reads it, updates its plan, and acts — immediately, literally, and at machine speed. There is no shrug. Whatever your error says, that becomes the agent's theory of what went wrong.

This is why misleading errors are so much more expensive than they used to be. Consider the classics:

  • The blame-shifting error: "Invalid request." Invalid how? The agent guesses, mutates a random field, and retries. Five attempts later it has explored a space of wrong hypotheses your message could have eliminated in one sentence.
  • The stale hint: "Did you mean --force?" — where the suggestion logic hasn't been updated since two releases ago. A human might notice the flag doesn't exist anymore. The agent runs it verbatim, gets a second error, and now has two layers of confusion in context.
  • The lying success: exit code 0 with a warning buried in stderr. Humans occasionally catch this. Agents almost never do — exit codes are the strongest signal in their loop, and you just told them everything worked.

Each of these was always bad. What's new is the multiplier. A confusing error in a popular CLI used to waste minutes of scattered human attention. Now it's executed-as-instruction by every agent fleet that touches your tool, in every retry, forever — until you fix the string.

Anatomy of an Error That Converges an Agent

The errors that work share a shape, and it looks a lot like a good prompt: state what happened, give the relevant context, and say what to do next. Concretely, an agent-legible error carries four things.

A named, stable error type. InputValidationError, RateLimited, FileNotFound — a classification the agent can pattern-match on. Practitioners building agent tooling consistently report that classifying errors into types with type-specific guidance beats a generic "try again" by a wide margin, because "re-read the file, it changed since your last read" is actionable and "operation failed" is not.

The offending value, echoed back. Not "invalid parameter" but "parameter region was us-esat-1; valid values are us-east-1, us-west-2". Echoing the input closes the gap between what the agent thinks it sent and what you received — a gap that is invisible to the agent and a shockingly common root cause, since models mangle strings in transit all the time.

A concrete next action. This is the part that turns an error from a dead end into a prompt. "Run auth login first." "Retry after 30 seconds." "Narrow the query; this one returned 14,000 rows and the limit is 50." Anthropic's guidance for tool builders makes this point directly: instead of a bare TOO_MANY_RESULTS, tell the agent to paginate or filter — the error message should guide the agent toward a better call, not just reject the current one.

An explicit prohibition when there's a known trap. Agents under pressure to make progress will reach for workarounds — skipping hooks, deleting lock files, force-pushing. If a failure mode has a tempting-but-destructive "fix," say so in the error: "Do not delete the migration file; run migrate repair instead." Negative guidance in a system prompt decays over a long session, but negative guidance delivered at the moment of the error lands exactly when it's needed.

One more field earns its keep in API contexts: retryability. A boolean retryable (or an RFC 7807-style structured body with a machine-readable type) lets the calling harness decide between retry, backoff, and escalate-to-human without burning a model call on the question. Structure that a deterministic wrapper can act on is even cheaper than prose the model has to interpret.

The Verbosity Trap: Your Stack Trace Is Eating the Context Window

The opposite failure is just as damaging. If terse errors starve the agent, verbose ones poison it.

A 400-line Java stack trace contains maybe three lines of signal. For a human with scroll and Ctrl-F, the other 397 lines are a nuisance. For an agent, they're a tax on everything that follows: they crowd out the actual code under discussion, they push earlier decisions out of the window, and they degrade the quality of every subsequent step. Context is a finite resource, and error output is one of its biggest unbudgeted consumers — agents routinely resort to piping build output through head just to survive it, which then costs them the very lines that mattered.

Worse, repetition compounds. A retry loop that hits the same failure five times accumulates five copies of the same trace. By the end, the context window is a landfill of redundant failure text, and the model starts pattern-matching on the noise — "corrupted state" caused by nothing more than uncurated error verbosity.

The fix is editorial, not technical:

  • Lead with the cause, not the plumbing. The first line should say what went wrong in domain terms. Frames and internals go below the fold, or behind a flag.
  • Truncate with a pointer, not an ellipsis. "Full trace written to /tmp/build-4821.log" preserves access without the token cost. An agent that needs the details can go read the file; one that doesn't saves two thousand tokens.
  • Deduplicate repeats. "Same error as previous attempt (×3)" is one line that tells the agent something a third identical trace cannot: your fix isn't working, change strategy.
  • Make verbosity a parameter. Concise by default, --verbose for the deep dive. Agents are good at asking for more when told the option exists.

The principle mirrors context engineering generally: the goal isn't more information, it's the smallest set of high-signal tokens that lets the next step succeed.

"Did You Mean?" at Fleet Scale

Suggestion hints deserve special attention because they are the most prompt-like part of any error surface. When git says "did you mean git status?", a human treats it as a suggestion. An agent treats it as the answer. Command-not-found handlers, fuzzy flag matching, "similar issues" links in test failures — every one of these is an autocomplete for the agent's next action, and agents accept the autocomplete at a rate humans never did.

That makes hint quality a leverage point in both directions. A good hint collapses the search space: typo'd subcommand, one retry, done. A bad hint is worse than none, because the agent will follow it before it follows its own judgment. If your fuzzy matcher suggests a semantically wrong-but-textually-close flag — --dry-run when the user typo'd --daemon — you haven't just failed to help; you've actively routed thousands of loops down a wrong path that looks like progress.

There's a subtler consequence: your hints now compete with the model's prior. When an agent hits an error in your tool, it arrives with training-data memories of similar tools and will happily import their conventions. The error message is your one chance to override that prior with ground truth — "this project uses yarn, not npm" at the moment of the failed npm install beats the same sentence sitting unread in a README. Tools that state their own conventions in their errors get followed; tools that assume convention knowledge get whatever the model's prior says, which may describe someone else's tool entirely.

Error Text Is API Design — Review It Like API Design

If errors are prompts, they deserve the process we give prompts and public interfaces, not the afterthought status they've had since the first panic().

Put error strings in review scope. A misleading message merges as easily as a correct one, because reviewers read error text as decoration. Treat changes to error copy like changes to an API response schema — because for your agent consumers, that's exactly what they are.

Eval recovery, not just failure. Most test suites assert that the right error fires. Almost none assert that a fresh agent, given only that error text, can recover. That second test is cheap to run now — hand the error to a model with the tool's docs and see whether its proposed next action is the right one. Anthropic's tool-building guidance is blunt on this: you don't get tool ergonomics right on the first try; you find the failures by watching agents actually hit them, then rewrite. Error messages are the highest-yield place to apply that loop, because they're pure text — fixing one is a string change, not a refactor.

Version your hints like behavior. If agents act on your suggestions, a changed suggestion is a behavior change. The "did you mean" logic that ships wrong advice for one release will have that advice executed, at scale, for the whole release window.

Watch your telemetry for spiral signatures. Repeated identical failures from the same session, rising --force/--no-verify usage after specific errors, retry counts clustered on one error type — these are your error messages failing as prompts. Each spike marks a string worth rewriting.

The teams that internalize this are quietly accumulating an advantage. Their tools converge agent loops in one or two retries; everyone else's burn ten. The gap doesn't show up in any benchmark — no eval measures "quality of your CLI's failure copy" — but it shows up in token bills, wall-clock time, and how often a human has to step in and un-stick a run.

The stack trace was designed in an era when the reader was the author, five minutes after writing the bug. Then it survived an era when the reader was a stranger with a search engine. The reader is now a model with your error text as its entire theory of the failure, deciding in the next hundred milliseconds what your system should do about it. Write for that reader. It's the most literal audience your prose will ever have — and the most likely to do exactly what you say.

References:Let's stay in touch and Follow me for more thoughts and updates