Skip to content

fix(agents): classify Anthropic orphaned tool-use replay errors#98163

Merged
vincentkoc merged 1 commit into
openclaw:mainfrom
masatohoshino:fix/anthropic-tool-use-conversation-state-classifier
Jun 30, 2026
Merged

fix(agents): classify Anthropic orphaned tool-use replay errors#98163
vincentkoc merged 1 commit into
openclaw:mainfrom
masatohoshino:fix/anthropic-tool-use-conversation-state-classifier

Conversation

@masatohoshino

@masatohoshino masatohoshino commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

When Anthropic (and Bedrock-on-Anthropic) rejects a request because the conversation has an orphaned tool call, the API returns a 400 whose message reads:

`tool_use` ids were found without `tool_result` blocks immediately after: ...

The user gets a generic failure with no guidance. Other provider conversation-state rejections that share the same broken-turn shape — OpenAI's missing custom tool output, Bedrock's toolResult/toolUse count mismatch, Gemini's function-call ordering, and role-alternation errors — already surface a recovery hint telling the user to retry or start a fresh session. This one phrasing was not recognized, so the same class of error produced an unhelpful message instead.

Why This Change Was Made

isProviderConversationStateErrorMessage in src/auto-reply/reply/provider-request-error-classifier.ts is the single place that recognizes provider-side broken conversation and tool-turn state, but it only matched the camelCase Bedrock count-mismatch wording. This adds one narrow clause that matches when the error text contains all of tool_use, tool_result, and without together, routing the Anthropic orphaned-tool-call 400 through the existing provider_conversation_state_error path. Requiring all three tokens — with without as the orphan signal — keeps generic prose that merely names both block types from matching. The change is limited to that classifier function: it adds no new error code, no recovery copy, and no provider behavior; the user-facing message and recovery flow are the ones that already exist for this category.

User Impact

When this specific provider rejection occurs, openclaw users now see the existing conversation-state guidance — "The model provider rejected the conversation state. Please try again, or use /new to start a fresh session." — instead of a generic failure, so the actionable next step is clear. No other classification, message, or behavior changes.

Evidence

Real production-path invocation (no mocks). A Node/tsx script imports the exported classifyProviderRequestError and PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE directly from src/auto-reply/reply/provider-request-error-classifier.ts, constructs a real Error carrying the Anthropic-style orphaned-tool-call rejection text, and prints the routed result. Redacted terminal output (the toolu_… id is synthetic and redacted; no user content or secrets are present):

input Error.message = "messages.57: `tool_use` ids were found without `tool_result` blocks immediately after: toolu_01<redacted>. Each `tool_use` block must have a corresponding `tool_result` block in the next message."
----------------------------------------------------------------
classifyProviderRequestError(err).code        = provider_conversation_state_error
classifyProviderRequestError(err).userMessage = "⚠️ The model provider rejected the conversation state. Please try again, or use /new to start a fresh session."
----------------------------------------------------------------
userMessage includes "use /new to start a fresh session" = true
userMessage === PROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGE = true

The real classifyProviderRequestError (which internally runs the real formatErrorMessage and isProviderConversationStateErrorMessage) returns code: provider_conversation_state_error and the existing recovery message, confirming the orphaned-tool-call rejection now reaches the "use /new to start a fresh session" guidance.

Deterministic unit coverage (src/auto-reply/reply/provider-request-error-classifier.test.ts): a verbatim Anthropic-style orphaned-tool-call 400 classifies as provider_conversation_state_error, and a generic message naming both block types but without the orphan signal stays unclassified (no over-match). Focused tests: 23 passed. Typecheck clean on the core prod (tsgo:core) and core-test (tsgo:test:src) lanes; oxfmt --check clean on both touched files.

AI-assisted.

@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 10:45 AM ET / 14:45 UTC.

Summary
The PR adds Anthropic tool_use/tool_result orphaned-tool-call wording to the shared provider request error classifier and adds positive and negative classifier tests.

PR surface: Source +4, Tests +14. Total +18 across 2 files.

Reproducibility: yes. at source level: current main's classifier does not match the Anthropic snake_case orphaned-tool-call message, so that exact provider rejection falls through to generic handling. I did not execute a live Anthropic replay in this read-only review.

Review metrics: none identified.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Next step before merge

  • No ClawSweeper repair job is needed because the PR already contains the narrow implementation and sufficient contributor-side terminal proof.

Security
Cleared: The diff only changes TypeScript string classification and adjacent tests, with no dependency, workflow, credential, install, or code-execution surface changes.

Review details

Best possible solution:

Land this narrow classifier-only fix if the normal CI and maintainer gates pass, while leaving broader retry/root-cause replay repair to the existing session-state items.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main's classifier does not match the Anthropic snake_case orphaned-tool-call message, so that exact provider rejection falls through to generic handling. I did not execute a live Anthropic replay in this read-only review.

Is this the best way to solve the issue?

Yes for this auto-reply failure path: adding the missing provider phrase to the existing classifier is the narrowest defensive fix and reuses established recovery copy. Broader retry-and-repair behavior remains a separate product/root-cause path.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 6cb82eaab865.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The updated PR body includes redacted terminal output from a production-path classifier invocation showing the Anthropic-style error maps to the existing recovery message.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The updated PR body includes redacted terminal output from a production-path classifier invocation showing the Anthropic-style error maps to the existing recovery message.
  • remove rating: 🦪 silver shellfish: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is a bounded user-facing provider error-message bug fix with limited blast radius and no config, data, or API surface change.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The updated PR body includes redacted terminal output from a production-path classifier invocation showing the Anthropic-style error maps to the existing recovery message.
  • proof: sufficient: Contributor real behavior proof is sufficient. The updated PR body includes redacted terminal output from a production-path classifier invocation showing the Anthropic-style error maps to the existing recovery message.
Evidence reviewed

PR surface:

Source +4, Tests +14. Total +18 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 4 0 +4
Tests 1 14 0 +14
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 18 0 +18

What I checked:

  • Root policy read: The full root AGENTS.md was read and applied; its PR review-depth, proof-gate, related-item, scoped-policy, and security-review guidance affected this review. (AGENTS.md:1, 6cb82eaab865)
  • Scoped policy check: No scoped AGENTS.md owns src/auto-reply/reply, and the only maintainer note present was Telegram-specific, so no additional scoped policy applied.
  • Current classifier gap: Current main classifies OpenAI missing custom tool output, Bedrock camelCase toolResult/toolUse mismatches, Gemini function-call ordering, role ordering, and local replay invariant text, but it has no snake_case tool_use/tool_result orphan clause. (src/auto-reply/reply/provider-request-error-classifier.ts:100, 6cb82eaab865)
  • Runtime caller path: External run failures call classifyProviderRequestError(error ?? normalizedMessage) before falling back to generic failure handling, so this classifier change affects real user-facing reply text without changing provider execution. (src/auto-reply/reply/agent-runner-execution.ts:960, 6cb82eaab865)
  • Main error path: The main run error path classifies non-billing provider request errors before generic handling when errors are not surfaced to Control UI. (src/auto-reply/reply/agent-runner-execution.ts:3134, 6cb82eaab865)
  • PR diff coverage: The PR adds one narrow condition requiring tool_use, tool_result, and without, plus a positive Anthropic-style fixture and a negative generic mention fixture. (src/auto-reply/reply/provider-request-error-classifier.ts:105, 641a96cf347a)

Likely related people:

  • dutifulbob: PR history shows dutifulbob authored the provider conversation-state classifier and recovery-copy path in fix: classify provider conversation state errors #82616. (role: introduced classifier surface; confidence: high; commits: 80d03a1e5bcc, d0b1dc385d13, f7da7d34a2cc; files: src/auto-reply/reply/provider-request-error-classifier.ts, src/auto-reply/reply/provider-request-error-classifier.test.ts, src/auto-reply/reply/agent-runner-execution.ts)
  • anyech: PR history shows anyech added replay-invariant classification and narrowed related conversation-state 400 handling in fix(agents): recover message-tool mirror replay poison #84708. (role: adjacent replay-classifier contributor; confidence: medium; commits: e90fb676414a, 04d3c8b11dbf, a67e5670eb11; files: src/auto-reply/reply/provider-request-error-classifier.ts, src/auto-reply/reply/provider-request-error-classifier.test.ts)
  • vincentkoc: CONTRIBUTING.md lists Vincent Koc for Agents, and recent auto-reply/agent-runner history includes his review, co-author, and merge activity around adjacent failure handling paths. (role: listed Agents area owner and recent reviewer; confidence: medium; commits: e998986889ec, 14e448e0e13d, eb5fb2aa69f4; files: CONTRIBUTING.md, src/auto-reply/reply/agent-runner-execution.ts, src/auto-reply/reply/provider-request-error-classifier.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 30, 2026
@vincentkoc
vincentkoc merged commit a75431c into openclaw:main Jun 30, 2026
209 of 219 checks passed
@vincentkoc

Copy link
Copy Markdown
Member

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 1, 2026
QiuYuang pushed a commit to QiuYuang/openclaw that referenced this pull request Jul 1, 2026
@masatohoshino
masatohoshino deleted the fix/anthropic-tool-use-conversation-state-classifier branch July 1, 2026 14:43
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 3, 2026
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 6, 2026
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants