Skip to content

Commit df763b3

Browse files
committed
fix(anthropic): strip all thinking blocks from historical messages
Strip ALL thinking blocks from historical assistant messages to avoid "Invalid signature in thinking block" errors when Anthropic thinking signatures expire. Anthropic thinking signatures are time-limited and single-use. Replaying them causes API errors that brick sessions until manual reset. The current-turn thinking block is preserved by the stream handler, not by transcript replay. Fixes #88932
1 parent 630f0d6 commit df763b3

2 files changed

Lines changed: 8 additions & 29 deletions

File tree

src/llm/providers/anthropic.test.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe("Anthropic provider", () => {
7979
expect(config.defaultHeaders?.["cf-aig-authorization"]).toBe("Bearer gateway-token");
8080
});
8181

82-
it("preserves provider-signed Anthropic thinking text on replay", async () => {
82+
it("strips all thinking blocks from historical messages to avoid expired signature errors", async () => {
8383
const highSurrogate = String.fromCharCode(0xd83d);
8484
const signedThinking = `keep${highSurrogate}signed`;
8585
let capturedPayload: unknown;
@@ -154,18 +154,7 @@ describe("Anthropic provider", () => {
154154

155155
const payload = capturedPayload as { messages: Array<{ role: string; content: unknown[] }> };
156156
const assistantMessage = payload.messages.find((message) => message.role === "assistant");
157-
expect(assistantMessage?.content).toEqual([
158-
{
159-
type: "thinking",
160-
thinking: signedThinking,
161-
signature: "sig_1",
162-
},
163-
{
164-
type: "thinking",
165-
thinking: "sanitizesynthetic",
166-
signature: "reasoning_content",
167-
},
168-
]);
157+
expect(assistantMessage?.content).toEqual([]);
169158
});
170159

171160
it("clamps max adaptive effort when the Claude model does not advertise it", async () => {

src/llm/providers/transform-messages.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,12 @@ export function transformMessages<TApi extends Api>(
107107
if (block.redacted) {
108108
return isSameModel ? block : [];
109109
}
110-
// For same model: keep thinking blocks with signatures (needed for replay)
111-
// even if the thinking text is empty (OpenAI encrypted reasoning)
112-
if (isSameModel && block.thinkingSignature) {
113-
return block;
114-
}
115-
// Skip empty thinking blocks, convert others to plain text
116-
if (!block.thinking || block.thinking.trim() === "") {
117-
return [];
118-
}
119-
if (isSameModel) {
120-
return block;
121-
}
122-
return {
123-
type: "text" as const,
124-
text: block.thinking,
125-
};
110+
// Strip ALL thinking blocks from historical assistant messages.
111+
// Anthropic thinking signatures are time-limited and single-use;
112+
// replaying them causes "Invalid signature in thinking block" errors.
113+
// The current-turn thinking block (if any) is preserved by the stream handler,
114+
// not by transcript replay.
115+
return [];
126116
}
127117

128118
if (block.type === "text") {

0 commit comments

Comments
 (0)