From 853762dea30582090d2c272ad3f42253953bd5bb Mon Sep 17 00:00:00 2001 From: jnjpng Date: Tue, 3 Mar 2026 18:36:21 -0800 Subject: [PATCH] fix: align headless 409 busy retry with TUI exponential backoff (#1253) Co-authored-by: Letta Code --- src/cli/App.tsx | 4 ++-- src/headless.ts | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 8ceff20..27d9101 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -328,8 +328,8 @@ const LLM_API_ERROR_MAX_RETRIES = 3; const EMPTY_RESPONSE_MAX_RETRIES = 2; // Retry config for 409 "conversation busy" errors (exponential backoff) -const CONVERSATION_BUSY_MAX_RETRIES = 3; // 2.5s -> 5s -> 10s -const CONVERSATION_BUSY_RETRY_BASE_DELAY_MS = 2500; // 2.5 seconds +const CONVERSATION_BUSY_MAX_RETRIES = 3; // 10s -> 20s -> 40s +const CONVERSATION_BUSY_RETRY_BASE_DELAY_MS = 10000; // 10 seconds // Message shown when user interrupts the stream const INTERRUPT_MESSAGE = diff --git a/src/headless.ts b/src/headless.ts index d43158e..8e9415f 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -131,9 +131,9 @@ const LLM_API_ERROR_MAX_RETRIES = 3; // Retry 1: same input. Retry 2: with system reminder nudge. const EMPTY_RESPONSE_MAX_RETRIES = 2; -// Retry config for 409 "conversation busy" errors -const CONVERSATION_BUSY_MAX_RETRIES = 1; // Only retry once, fail on 2nd 409 -const CONVERSATION_BUSY_RETRY_DELAY_MS = 2500; // 2.5 seconds +// Retry config for 409 "conversation busy" errors (exponential backoff) +const CONVERSATION_BUSY_MAX_RETRIES = 3; // 10s -> 20s -> 40s +const CONVERSATION_BUSY_RETRY_BASE_DELAY_MS = 10000; // 10 seconds export type BidirectionalQueuedInput = QueuedTurnInput< MessageCreate["content"] @@ -1553,6 +1553,9 @@ ${SYSTEM_REMINDER_CLOSE} // Check for 409 "conversation busy" error - retry once with delay if (preStreamAction === "retry_conversation_busy") { conversationBusyRetries += 1; + const retryDelayMs = + CONVERSATION_BUSY_RETRY_BASE_DELAY_MS * + 2 ** (conversationBusyRetries - 1); // Emit retry message for stream-json mode if (outputFormat === "stream-json") { @@ -1561,21 +1564,19 @@ ${SYSTEM_REMINDER_CLOSE} reason: "error", // 409 conversation busy is a pre-stream error attempt: conversationBusyRetries, max_attempts: CONVERSATION_BUSY_MAX_RETRIES, - delay_ms: CONVERSATION_BUSY_RETRY_DELAY_MS, + delay_ms: retryDelayMs, session_id: sessionId, uuid: `retry-conversation-busy-${randomUUID()}`, }; console.log(JSON.stringify(retryMsg)); } else { console.error( - `Conversation is busy, waiting ${CONVERSATION_BUSY_RETRY_DELAY_MS / 1000}s and retrying...`, + `Conversation is busy, waiting ${Math.round(retryDelayMs / 1000)}s and retrying...`, ); } // Wait before retry - await new Promise((resolve) => - setTimeout(resolve, CONVERSATION_BUSY_RETRY_DELAY_MS), - ); + await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); continue; }