From 7abefcca1fa6ed23c899462d538328345b2c4a5d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 8 Feb 2026 13:16:16 +0900 Subject: [PATCH] feat: auto-recover from Anthropic assistant message prefill errors When Anthropic models reject requests with 'This model does not support assistant message prefill', detect this as a recoverable error type and automatically send 'Continue' once to resume the conversation. Extends session-recovery hook with new 'assistant_prefill_unsupported' error type. The existing session.error handler in index.ts already sends 'continue' after successful recovery, so no additional logic needed. --- src/hooks/session-recovery/index.test.ts | 57 ++++++++++++++++++++++++ src/hooks/session-recovery/index.ts | 12 +++++ 2 files changed, 69 insertions(+) diff --git a/src/hooks/session-recovery/index.test.ts b/src/hooks/session-recovery/index.test.ts index 93d7990a..257fe05a 100644 --- a/src/hooks/session-recovery/index.test.ts +++ b/src/hooks/session-recovery/index.test.ts @@ -129,6 +129,63 @@ describe("detectErrorType", () => { }) }) + describe("assistant_prefill_unsupported errors", () => { + it("should detect assistant message prefill error from direct message", () => { + //#given an error about assistant message prefill not being supported + const error = { + message: "This model does not support assistant message prefill. The conversation must end with a user message.", + } + + //#when detectErrorType is called + const result = detectErrorType(error) + + //#then should return assistant_prefill_unsupported + expect(result).toBe("assistant_prefill_unsupported") + }) + + it("should detect assistant message prefill error from nested error object", () => { + //#given an Anthropic API error with nested structure matching the real error format + const error = { + error: { + type: "invalid_request_error", + message: "This model does not support assistant message prefill. The conversation must end with a user message.", + }, + } + + //#when detectErrorType is called + const result = detectErrorType(error) + + //#then should return assistant_prefill_unsupported + expect(result).toBe("assistant_prefill_unsupported") + }) + + it("should detect error with only 'conversation must end with a user message' fragment", () => { + //#given an error containing only the user message requirement + const error = { + message: "The conversation must end with a user message.", + } + + //#when detectErrorType is called + const result = detectErrorType(error) + + //#then should return assistant_prefill_unsupported + expect(result).toBe("assistant_prefill_unsupported") + }) + + it("should detect error with only 'assistant message prefill' fragment", () => { + //#given an error containing only the prefill mention + const error = { + message: "This model does not support assistant message prefill.", + } + + //#when detectErrorType is called + const result = detectErrorType(error) + + //#then should return assistant_prefill_unsupported + expect(result).toBe("assistant_prefill_unsupported") + }) + }) + describe("unrecognized errors", () => { it("should return null for unrecognized error patterns", () => { // given an unrelated error diff --git a/src/hooks/session-recovery/index.ts b/src/hooks/session-recovery/index.ts index 2aecee15..9f73c074 100644 --- a/src/hooks/session-recovery/index.ts +++ b/src/hooks/session-recovery/index.ts @@ -28,6 +28,7 @@ type RecoveryErrorType = | "tool_result_missing" | "thinking_block_order" | "thinking_disabled_violation" + | "assistant_prefill_unsupported" | null interface MessageInfo { @@ -126,6 +127,13 @@ function extractMessageIndex(error: unknown): number | null { export function detectErrorType(error: unknown): RecoveryErrorType { const message = getErrorMessage(error) + if ( + message.includes("assistant message prefill") || + message.includes("conversation must end with a user message") + ) { + return "assistant_prefill_unsupported" + } + // IMPORTANT: Check thinking_block_order BEFORE tool_result_missing // because Anthropic's extended thinking error messages contain "tool_use" and "tool_result" // in the documentation URL, which would incorrectly match tool_result_missing @@ -375,11 +383,13 @@ export function createSessionRecoveryHook(ctx: PluginInput, options?: SessionRec tool_result_missing: "Tool Crash Recovery", thinking_block_order: "Thinking Block Recovery", thinking_disabled_violation: "Thinking Strip Recovery", + assistant_prefill_unsupported: "Prefill Error Recovery", } const toastMessages: Record = { tool_result_missing: "Injecting cancelled tool results...", thinking_block_order: "Fixing message structure...", thinking_disabled_violation: "Stripping thinking blocks...", + assistant_prefill_unsupported: "Sending 'Continue' to recover...", } await ctx.client.tui @@ -411,6 +421,8 @@ export function createSessionRecoveryHook(ctx: PluginInput, options?: SessionRec const resumeConfig = extractResumeConfig(lastUser, sessionID) await resumeSession(ctx.client, resumeConfig) } + } else if (errorType === "assistant_prefill_unsupported") { + success = true } return success