Skip to content

Commit b4f533e

Browse files
committed
fix(embedded-agent): treat stale thinking-block signature as replay-invalid
Anthropic rejects the next call with 'Invalid signature in thinking block' once early extended-thinking signatures expire on a long session. REPLAY_INVALID_RE did not match it, so classifyProviderRuntimeFailureKind fell through and the session hard-failed instead of routing to replay recovery (strip stale thinking blocks and retry). Add the pattern so the message classifies as replay_invalid.
1 parent 3142c97 commit b4f533e

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

src/agents/embedded-agent-helpers/errors.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
33
import type { OpenClawConfig } from "../../config/types.openclaw.js";
44
import { MALFORMED_STREAMING_FRAGMENT_ERROR_MESSAGE } from "../../shared/assistant-error-format.js";
55
import { makeAssistantMessageFixture } from "../test-helpers/assistant-message-fixtures.js";
6-
import { formatAssistantErrorText, isLikelyContextOverflowError } from "./errors.js";
6+
import {
7+
classifyProviderRuntimeFailureKind,
8+
formatAssistantErrorText,
9+
isLikelyContextOverflowError,
10+
} from "./errors.js";
711

812
const { toolPolicyAuditInfo } = vi.hoisted(() => ({
913
toolPolicyAuditInfo: vi.fn(),
@@ -102,3 +106,18 @@ describe("isLikelyContextOverflowError", () => {
102106
).toBe(true);
103107
});
104108
});
109+
110+
describe("classifyProviderRuntimeFailureKind replay-invalid", () => {
111+
it("classifies Anthropic stale thinking-block signatures as replay_invalid", () => {
112+
// Expired extended-thinking signatures must route to replay recovery
113+
// (strip stale thinking blocks and retry), not a hard session failure.
114+
expect(classifyProviderRuntimeFailureKind("Invalid signature in thinking block")).toBe(
115+
"replay_invalid",
116+
);
117+
expect(
118+
classifyProviderRuntimeFailureKind(
119+
"messages.3.content.0: Invalid signature in thinking block at index 0",
120+
),
121+
).toBe("replay_invalid");
122+
});
123+
});

src/agents/embedded-agent-helpers/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ const DNS_ERROR_RE = /\benotfound\b|\beai_again\b|\bgetaddrinfo\b|\bno such host
353353
const INTERRUPTED_NETWORK_ERROR_RE =
354354
/\beconnrefused\b|\beconnreset\b|\beconnaborted\b|\benetreset\b|\behostunreach\b|\behostdown\b|\benetunreach\b|\bepipe\b|\bsocket hang up\b|\bconnection refused\b|\bconnection reset\b|\bconnection aborted\b|\bnetwork is unreachable\b|\bhost is unreachable\b|\bfetch failed\b|\bconnection error\b|\bnetwork request failed\b/i;
355355
const REPLAY_INVALID_RE =
356-
/\bprevious_response_id\b.*\b(?:invalid|unknown|not found|does not exist|expired|mismatch)\b|\btool_(?:use|call)\.(?:input|arguments)\b.*\b(?:missing|required)\b|\bincorrect role information\b|\broles must alternate\b|\binput item id does not belong to this connection\b/i;
356+
/\bprevious_response_id\b.*\b(?:invalid|unknown|not found|does not exist|expired|mismatch)\b|\btool_(?:use|call)\.(?:input|arguments)\b.*\b(?:missing|required)\b|\bincorrect role information\b|\broles must alternate\b|\binput item id does not belong to this connection\b|\binvalid signature\b.*?\bthinking block\b/i;
357357
const SANDBOX_BLOCKED_RE =
358358
/\bapproval is required\b|\bapproval timed out\b|\bapproval was denied\b|\bblocked by sandbox\b|\bsandbox\b.*\b(?:blocked|denied|forbidden|disabled|not allowed)\b|\bexec denied\s*\(/i;
359359
const NO_BODY_HTTP_WRAPPER_RE =

0 commit comments

Comments
 (0)