diff --git a/src/agents/subagent-announce-queue.ts b/src/agents/subagent-announce-queue.ts index c81dd94b1d9..611541c186e 100644 --- a/src/agents/subagent-announce-queue.ts +++ b/src/agents/subagent-announce-queue.ts @@ -48,6 +48,8 @@ type AnnounceQueueState = { droppedCount: number; summaryLines: string[]; send: (item: AnnounceQueueItem) => Promise; + /** Consecutive drain failures — drives exponential backoff on errors. */ + consecutiveFailures: number; }; const ANNOUNCE_QUEUES = new Map(); @@ -89,6 +91,7 @@ function getAnnounceQueue( droppedCount: 0, summaryLines: [], send, + consecutiveFailures: 0, }; applyQueueRuntimeSettings({ target: created, @@ -174,10 +177,16 @@ function scheduleAnnounceDrain(key: string) { break; } } + // Drain succeeded — reset failure counter. + queue.consecutiveFailures = 0; } catch (err) { - // Keep items in queue and retry after debounce; avoid hot-loop retries. - queue.lastEnqueuedAt = Date.now(); - defaultRuntime.error?.(`announce queue drain failed for ${key}: ${String(err)}`); + queue.consecutiveFailures++; + // Exponential backoff on consecutive failures: 2s, 4s, 8s, ... capped at 60s. + const errorBackoffMs = Math.min(1000 * Math.pow(2, queue.consecutiveFailures), 60_000); + queue.lastEnqueuedAt = Date.now() + errorBackoffMs - queue.debounceMs; + defaultRuntime.error?.( + `announce queue drain failed for ${key} (attempt ${queue.consecutiveFailures}, retry in ${Math.round(errorBackoffMs / 1000)}s): ${String(err)}`, + ); } finally { queue.draining = false; if (queue.items.length === 0 && queue.droppedCount === 0) {