From 7fdd163665082bc6893a3b7ac73b23e84ddf105b Mon Sep 17 00:00:00 2001 From: cthomas Date: Thu, 19 Feb 2026 11:57:56 -0800 Subject: [PATCH] fix: dont retry on agents limit exceeded (#1035) --- src/agent/turn-recovery-policy.ts | 12 +++++++++++- src/tests/turn-recovery-policy.test.ts | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/agent/turn-recovery-policy.ts b/src/agent/turn-recovery-policy.ts index ce76efc..1044fa8 100644 --- a/src/agent/turn-recovery-policy.ts +++ b/src/agent/turn-recovery-policy.ts @@ -46,6 +46,7 @@ const NON_RETRYABLE_PROVIDER_DETAIL_PATTERNS = [ "context_length_exceeded", "invalid_encrypted_content", ]; +const NON_RETRYABLE_429_REASONS = ["agents-limit-exceeded"]; const NON_RETRYABLE_4XX_PATTERN = /Error code:\s*4(0[0-8]|1\d|2\d|3\d|4\d|51)/i; const RETRYABLE_429_PATTERN = /Error code:\s*429|rate limit|too many requests/i; @@ -109,7 +110,16 @@ export function shouldRetryPreStreamTransientError(opts: { detail: unknown; }): boolean { const { status, detail } = opts; - if (status === 429) return true; + if (status === 429) { + // Don't retry non-recoverable 429s (e.g. agent limit reached) + if ( + typeof detail === "string" && + NON_RETRYABLE_429_REASONS.some((r) => detail.includes(r)) + ) { + return false; + } + return true; + } if (status !== undefined && status >= 500) return true; if (status !== undefined && status >= 400) return false; diff --git a/src/tests/turn-recovery-policy.test.ts b/src/tests/turn-recovery-policy.test.ts index 4b876fe..aa7b4dc 100644 --- a/src/tests/turn-recovery-policy.test.ts +++ b/src/tests/turn-recovery-policy.test.ts @@ -210,6 +210,14 @@ describe("provider detail retry helpers", () => { detail: "rate limited", }), ).toBe(true); + // Non-recoverable 429: agents-limit-exceeded should NOT retry + expect( + shouldRetryPreStreamTransientError({ + status: 429, + detail: + '429 {"error":"Rate limited","reasons":["agents-limit-exceeded"]}', + }), + ).toBe(false); expect( shouldRetryPreStreamTransientError({ status: 401,