diff --git a/src/cliproxy/executor/retry-handler.ts b/src/cliproxy/executor/retry-handler.ts index dd850450..514af33f 100644 --- a/src/cliproxy/executor/retry-handler.ts +++ b/src/cliproxy/executor/retry-handler.ts @@ -125,6 +125,8 @@ export function detectFailedTier( /** Check if Claude exit indicates provider error (vs normal user exit) */ export function isProviderError(exitCode: number, stderr: string): boolean { + // Exit code 0 means success, even if stderr has error-like output + // (could be warnings, debug info, etc.) if (exitCode === 0) return false; return PROVIDER_ERROR_PATTERNS.some((p) => p.test(stderr)); } diff --git a/tests/unit/cliproxy/composite-fallback.test.ts b/tests/unit/cliproxy/composite-fallback.test.ts index 919919eb..338f6a02 100644 --- a/tests/unit/cliproxy/composite-fallback.test.ts +++ b/tests/unit/cliproxy/composite-fallback.test.ts @@ -147,6 +147,20 @@ describe('detectFailedTier', () => { // Should still match because model name is substring expect(result).toBe('opus'); }); + + it('should strip complex thinking suffix with comma-separated params', () => { + const tiersWithComplexBudget: typeof tiers = { + opus: { provider: 'agy', model: 'claude-opus-4-6-thinking(32768,extended)' }, + sonnet: { provider: 'agy', model: 'claude-sonnet-4-5-thinking(high)' }, + haiku: { provider: 'agy', model: 'claude-3-5-haiku' }, + }; + + // Stderr contains base model name without suffix + const stderr = 'Error: claude-opus-4-6-thinking overloaded'; + const result = detectFailedTier(stderr, tiersWithComplexBudget); + // Should match because regex strips (32768,extended) to get base name + expect(result).toBe('opus'); + }); }); // ========================================