Skip to content

Commit a75431c

Browse files
fix(agents): classify Anthropic orphaned tool-use replay errors (#98163)
1 parent 169acd1 commit a75431c

2 files changed

Lines changed: 18 additions & 0 deletions

File tree

src/auto-reply/reply/provider-request-error-classifier.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ describe("provider request error classifier", () => {
113113
"local replay invariant guard",
114114
"invalid_replay_transcript: OpenAI Responses replay contains dangling_tool_call toolCallId=call_1 at message index 4",
115115
],
116+
[
117+
"Anthropic orphaned tool_use replay",
118+
"messages.1: `tool_use` ids were found without `tool_result` blocks immediately after: toolu_01A09q90qw90lq917835lq9. Each `tool_use` block must have a corresponding `tool_result` block in the next message.",
119+
],
116120
])("classifies %s as provider conversation-state errors", (_label, message) => {
117121
expect(classifyProviderRequestError(new Error(message))).toEqual({
118122
code: "provider_conversation_state_error",
@@ -125,6 +129,16 @@ describe("provider request error classifier", () => {
125129
expect(classifyProviderRequestError(new Error("400 status code (no body)"))).toBeUndefined();
126130
});
127131

132+
it("does not classify generic tool_use/tool_result mentions without the orphan signal", () => {
133+
// Both block names appear but there is no "without" orphan signal, so this
134+
// generic guidance text must not trip the conversation-state classifier.
135+
expect(
136+
classifyProviderRequestError(
137+
new Error("Each tool_use block must have a corresponding tool_result block."),
138+
),
139+
).toBeUndefined();
140+
});
141+
128142
it("leaves explicit HTTP 429 rate-limit failures on the existing rate-limit path", () => {
129143
expect(classifyProviderRequestError(new Error("429: rate limit exceeded"))).toBeUndefined();
130144
});

src/auto-reply/reply/provider-request-error-classifier.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export function isProviderConversationStateErrorMessage(message: string): boolea
105105
lower.includes("tooluse") &&
106106
lower.includes("exceeds the number") &&
107107
lower.includes("previous turn")) ||
108+
// Anthropic/Bedrock orphaned tool-call replay: "`tool_use` ids were found
109+
// without `tool_result` blocks immediately after: ...". Same broken-turn
110+
// shape as the toolResult/toolUse count mismatch above, just snake_case.
111+
(lower.includes("tool_use") && lower.includes("tool_result") && lower.includes("without")) ||
108112
lower.includes("function call turn comes immediately after") ||
109113
lower.includes("incorrect role information") ||
110114
lower.includes("roles must alternate") ||

0 commit comments

Comments
 (0)