From ca8f8a8c1419837e7d781088e055da4fc77d7179 Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Sun, 1 Feb 2026 20:36:49 -0800 Subject: [PATCH] fix: add exponential backoff to 409 busy (#780) --- src/cli/App.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 999b909..af4ed30 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -231,9 +231,9 @@ const EAGER_CANCEL = true; // Maximum retries for transient LLM API errors (matches headless.ts) const LLM_API_ERROR_MAX_RETRIES = 3; -// 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; // 2.5s -> 5s -> 10s +const CONVERSATION_BUSY_RETRY_BASE_DELAY_MS = 2500; // 2.5 seconds // Message shown when user interrupts the stream const INTERRUPT_MESSAGE = @@ -2898,6 +2898,9 @@ export default function App({ conversationBusyRetriesRef.current < CONVERSATION_BUSY_MAX_RETRIES ) { conversationBusyRetriesRef.current += 1; + const retryDelayMs = + CONVERSATION_BUSY_RETRY_BASE_DELAY_MS * + 2 ** (conversationBusyRetriesRef.current - 1); // Show status message const statusId = uid("status"); @@ -2912,10 +2915,7 @@ export default function App({ // Wait with abort checking (same pattern as LLM API error retry) let cancelled = false; const startTime = Date.now(); - while ( - Date.now() - startTime < - CONVERSATION_BUSY_RETRY_DELAY_MS - ) { + while (Date.now() - startTime < retryDelayMs) { if ( abortControllerRef.current?.signal.aborted || userCancelledRef.current