fix(agents): classify Anthropic orphaned tool-use replay errors#98163
Conversation
|
Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 10:45 AM ET / 14:45 UTC. Summary 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 follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Next step before merge
Security Review detailsBest 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 changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +4, Tests +14. Total +18 across 2 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
|
Merged via squash.
|
…claw#98163) (cherry picked from commit a75431c)
…claw#98163) (cherry picked from commit a75431c)
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:
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
isProviderConversationStateErrorMessageinsrc/auto-reply/reply/provider-request-error-classifier.tsis 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 oftool_use,tool_result, andwithouttogether, routing the Anthropic orphaned-tool-call 400 through the existingprovider_conversation_state_errorpath. Requiring all three tokens — withwithoutas 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,
openclawusers 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
classifyProviderRequestErrorandPROVIDER_CONVERSATION_STATE_ERROR_USER_MESSAGEdirectly fromsrc/auto-reply/reply/provider-request-error-classifier.ts, constructs a realErrorcarrying the Anthropic-style orphaned-tool-call rejection text, and prints the routed result. Redacted terminal output (thetoolu_…id is synthetic and redacted; no user content or secrets are present):The real
classifyProviderRequestError(which internally runs the realformatErrorMessageandisProviderConversationStateErrorMessage) returnscode: provider_conversation_state_errorand 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 asprovider_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 --checkclean on both touched files.AI-assisted.