---
title: Retry, Fix, or Ask? Refusal States AI Agents Can Act On
description: Every refusal on one production MCP server said 'a person must decide', even a typo. Four refusal states agents can act on: fix args, retry, wait, ask the user.
canonical: https://spiritroom.app/blog/agent-refusal-states/
published: 2026-10-21
updated: 2026-10-21
---

> Markdown version of https://spiritroom.app/blog/agent-refusal-states/ for agents. Index: /llms.txt

[Blog](/blog/) / [Tool results and errors](/guides/tool-results-and-errors-for-agents/)

# Retry, fix the arguments, or ask the human? Refusal states agents can act on

When your tool says no, the agent has to decide: fix its call, try again, wait, or stop and ask the user. If every refusal says “ask a human”, a careful agent interrupts the user for its own typo. If none do, a careless one retries a spend. Here's what we found on a production server, and a small set of states that fixes it.

Leonid Bugaev · Published 21 Oct 2026

**TL;DR**

- **One label for everything:** on a 99-tool production server, 3 of 3 malformed ids and a version conflict all came back as “a person must decide”.
- **Text and state disagreed:** the version conflict's text said “re-read, then retry”; its state said “do not repeat the change automatically”.
- **Use four states:** fix the arguments, retry, wait or change state first, needs the user.
- **Name the next step:** the tool to call or the value to use, once, not a loop.
- **Clients refuse too:** Codex in headless mode refused every tool marked destructive, so honest annotations matter.
[One state for every refusal](#problem)[Four states agents can act on](#states)[Before and after](#examples)[Refusals from the agent app](#client)[Rules](#rules)[How we measured](#how-we-measured)[FAQ](#faq)

## One state for every refusal

A 99-tool production MCP server we tested adds a guidance block to every refusal. One of its states, “human input”, means a person must decide before the agent continues. We sent it the kinds of mistakes agents make, with no changes to data:

| Call | What was wrong | Guidance state |
| --- | --- | --- |
| Update a record with id “not-a-uuid” | Malformed id: the agent's mistake | Needs a person |
| Submit feedback with id “not-a-uuid” | Malformed id | Needs a person |
| Read a record with id “not-a-uuid” | Malformed id | Needs a person |
| Change a record with a stale version | Someone changed it since the read | Needs a person, “do not repeat the change automatically” |

The version conflict is the clearest case. Its text said: *“The record changed since you read it … re-read its current version, then retry.”* Its guidance said the opposite. An agent that trusts the structured field stops and asks the user about a routine retry. One that trusts the text retries. Which half it sees depends on the app; see [structuredContent or text](/blog/mcp-structuredcontent-vs-text/).

## Four states agents can act on

| State | Means | Agent should | Examples |
| --- | --- | --- | --- |
| `fix_args` | The call was wrong | Fix it from the message and call again | Malformed id, unknown enum value, wrong field name |
| `retry` | The world moved; the call was fine | Re-read (or wait briefly), then retry once | Version conflict, rate limit, timeout |
| `wrong_state` | Not possible yet | Call the named tool that changes the state, or wait for it | Must preview before start, run already in progress |
| `needs_user` | A person must decide | Ask the user one clear question | Spend over a budget, send a message, delete data |

Another production server we looked at uses a similar typed vocabulary (states for missing input, needed confirmation, wrong phase, not open yet and locked) and tells the agent to do what a refusal names once before reporting a problem. The names matter less than the split: **the agent's mistake, a transient state, a state to change, a human decision**.

**Find out what your refusals tell agents to do.**

We send your server the mistakes agents actually make, record what each refusal says, and test whether Claude Code, Codex and other apps recover.

[Test your MCP →](/#contact)

## Before and after

```
Before
  text:     The record changed since you read it. Re-read it, then retry.
  guidance: {"state": "human_input",
             "reason": "Do not repeat the change automatically."}

After
  text:     version 3 is stale (current: 4). Call get_record, then retry with expectedVersion 4.
  guidance: {"state": "retry", "next": {"do": "get_record", "for": {"id": "r_123"}}}
```

```
Before
  text:     Invalid arguments … (type tree)
  guidance: {"state": "human_input"}

After
  text:     id "not-a-uuid" is not a valid id. Use an id from list_records.
  guidance: {"state": "fix_args", "next": {"do": "list_records"}}
```

Keep the text and the state saying the same thing, so it doesn't matter which half the app shows. More on wording: [error messages for AI agents](/blog/error-messages-for-ai-agents/).

## Refusals from the agent app

Not every refusal comes from your server. In our live write-shape check, Codex in headless mode refused every tool the server marked destructive, because its approval policy was “never”, unless that tool was approved one by one. That's the app protecting the user, and it depends on your annotations being honest. On the same server, 2 credit-spending tools were marked non-destructive, so that protection wouldn't apply to them. See [tool annotations agents rely on](/blog/mcp-tool-annotations-destructive-hint/).

**Go deeper:** [Results that say what to do next](/blog/next-step-hints-in-tool-results/), [Confirmation flows for spending tools](/blog/mcp-two-step-confirm-spend/), [The 12,689-token typo](/blog/verbose-validation-errors-tokens/).

## Rules

1. **Return refusals as tool results** with `isError: true`, as the [MCP spec](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) recommends, so the model sees them.
2. **Pick one of four states**; reserve “needs the user” for real decisions.
3. **Name the next step**: the tool, the value, the id.
4. **Say “once”**: retry or fix once, then report, so agents don't loop.
5. **Make text and structured guidance agree.**
6. **Test it**: send the mistakes agents make and check what each refusal says. Our read-only live jobs never hit these errors, so we haven't measured how often agents stop on them; the mismatch is visible from the responses alone.

All the rules for results and errors: [tool results and errors for AI agents](/guides/tool-results-and-errors-for-agents/).

## How we measured

- **Refusal capture:** 7 calls to a 99-tool production MCP server on 16 Oct 2026: 1 preview, 1 no-op withdraw, 1 stage refusal, 1 version conflict and 3 validation errors. The record was re-read before and after and was unchanged; nothing was spent or sent.
- **Malformed ids:** 3 calls with “not-a-uuid”, repeated in 3 identical snapshot runs.
- **Codex:** Codex CLI 0.161.0 in headless exec mode, same day.
- **Limits:** one server; behaviour after a refusal not measured live. Tool names and texts are simplified.

## FAQ

### How should an MCP tool tell an agent whether to retry?

Return the refusal as a tool result with isError: true, one line saying what went wrong, and a state the agent can act on: fix the arguments, retry (after re-reading), wait or change state first, or needs the user. Name the exact next step.

### When should a tool ask the agent to involve the user?

Only for real decisions: spending money or credits, sending messages, deleting data, or choosing between options the user cares about. Not for typos, wrong ids or stale versions, which the agent can fix itself.

### What's wrong with telling agents never to retry?

Agents that follow it stop on mistakes they could fix. On the server we tested, a stale-version conflict, normally fixed by re-reading and retrying, told the agent not to repeat the change.

### Should refusal states be machine-readable?

Yes. Put the state in a small field such as guidance.state, and keep the text consistent with it. Our two-marker test showed some apps show the model only structuredContent and others only text, so the essential instruction should appear in both.

**Find out what your refusals tell agents to do.**

We send your server the mistakes agents actually make, record what each refusal says, and test whether Claude Code, Codex and other apps recover.

[Test your MCP →](/#contact)

## Keep reading

- Up[Tool results and errors for AI agents (guide)](/guides/tool-results-and-errors-for-agents/)
- Related[How to write error messages for AI agents: before and after, from a production MCP server](/blog/error-messages-for-ai-agents/)
- Related[Next-step hints: make every tool result tell the agent what to do next](/blog/next-step-hints-in-tool-results/)
- Related[Stop agents spending money without asking: the two-step confirm pattern for MCP tools](/blog/mcp-two-step-confirm-spend/)
