From 76b12a64fc2e2aaca1979769e084cbb04c405a6c Mon Sep 17 00:00:00 2001 From: Ricardo-M-L <69202550+Ricardo-M-L@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:21:42 +0800 Subject: [PATCH] fix(api): validate retry count environment values (#2172) --- src/services/api/withRetry.test.ts | 48 ++++++++++++++++++++++++++++-- src/services/api/withRetry.ts | 6 ++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/services/api/withRetry.test.ts b/src/services/api/withRetry.test.ts index 7134c6171..78cc50324 100644 --- a/src/services/api/withRetry.test.ts +++ b/src/services/api/withRetry.test.ts @@ -149,10 +149,54 @@ describe('retry configuration', () => { expect(getDefaultMaxRetries()).toBe(3) }) - test('falls back to default retry attempts for invalid values', async () => { - process.env.OPENCLAUDE_MAX_RETRIES = 'nope' + test('falls back to default retry attempts for malformed values', async () => { const { getDefaultMaxRetries } = await importFreshWithRetryModule() + const malformedValues = [ + 'nope', + ' ', + '5abc', + '2.5', + '-1', + 'Infinity', + '9'.repeat(400), + ] + for (const value of malformedValues) { + process.env.OPENCLAUDE_MAX_RETRIES = value + expect(getDefaultMaxRetries()).toBe(10) + } + }) + + test('uses default retries for a whitespace-only legacy setting', async () => { + process.env.CLAUDE_CODE_MAX_RETRIES = ' ' + process.env.OPENCLAUDE_RETRY_DELAY_MS = '1' + const { getDefaultMaxRetries, withRetry } = + await importFreshWithRetryModule() + const retryableError = APIError.generate( + 500, + undefined, + 'internal server error', + new Headers(), + ) + let attempts = 0 + expect(getDefaultMaxRetries()).toBe(10) + const result = await drainAsyncGenerator( + withRetry( + async () => ({} as Anthropic), + async () => { + attempts++ + if (attempts === 1) throw retryableError + return { ok: true } + }, + { + model: 'test-model', + thinkingConfig: { type: 'disabled' }, + }, + ), + ) + + expect(result).toEqual({ ok: true }) + expect(attempts).toBe(2) }) test('caps retry attempts to a bounded value', async () => { diff --git a/src/services/api/withRetry.ts b/src/services/api/withRetry.ts index 594000bc0..757460c1c 100644 --- a/src/services/api/withRetry.ts +++ b/src/services/api/withRetry.ts @@ -1014,8 +1014,10 @@ function validateRetryAttemptsEnvVar( if (!value) { return DEFAULT_MAX_RETRIES } - const parsed = parseInt(value, 10) - if (isNaN(parsed) || parsed < 0) { + const normalized = value.trim() + const isDecimalInteger = /^\d+$/.test(normalized) + const parsed = isDecimalInteger ? Number(normalized) : Number.NaN + if (!Number.isFinite(parsed)) { logForDebugging( `${envVarName} Invalid value "${value}" (using default: ${DEFAULT_MAX_RETRIES})`, )