From 5da63347367564104b2cc514c94d1f1fc1519996 Mon Sep 17 00:00:00 2001 From: "Kai (Tam Nhu) Tran" <61256810+kaitranntt@users.noreply.github.com> Date: Sat, 30 May 2026 14:54:45 -0400 Subject: [PATCH] fix(persist): bound Codex translator receipt scan --- src/shared/stale-codex-translator-settings.ts | 72 +++++++++++++++---- .../commands/persist-command-handler.test.ts | 37 ++++++++++ .../stale-codex-translator-settings.test.ts | 10 +++ 3 files changed, 104 insertions(+), 15 deletions(-) diff --git a/src/shared/stale-codex-translator-settings.ts b/src/shared/stale-codex-translator-settings.ts index 81156690..e0446ffd 100644 --- a/src/shared/stale-codex-translator-settings.ts +++ b/src/shared/stale-codex-translator-settings.ts @@ -1,5 +1,8 @@ export const CODEX_TRANSLATOR_URL_MARKER = '/api/provider/codex'; +const MAX_CODEX_TRANSLATOR_SCAN_DEPTH = 100; +const MAX_CODEX_TRANSLATOR_SCAN_NODES = 10000; + function formatSettingsPathSegment(basePath: string, segment: string | number): string { if (typeof segment === 'number') { return `${basePath}[${segment}]`; @@ -13,23 +16,62 @@ function formatSettingsPathSegment(basePath: string, segment: string | number): } export function findCodexTranslatorUrlPaths(value: unknown, path = ''): string[] { - if (typeof value === 'string') { - return value.includes(CODEX_TRANSLATOR_URL_MARKER) ? [path || '(root)'] : []; + const matches: string[] = []; + const stack: Array<{ value: unknown; path: string; depth: number }> = [{ value, path, depth: 0 }]; + const seen = new WeakSet(); + let visitedNodes = 0; + + while (stack.length > 0 && visitedNodes < MAX_CODEX_TRANSLATOR_SCAN_NODES) { + const item = stack.pop(); + if (!item) { + break; + } + + visitedNodes += 1; + + if (typeof item.value === 'string') { + if (item.value.includes(CODEX_TRANSLATOR_URL_MARKER)) { + matches.push(item.path || '(root)'); + } + continue; + } + + if (typeof item.value !== 'object' || item.value === null) { + continue; + } + + if (seen.has(item.value)) { + continue; + } + seen.add(item.value); + + if (item.depth >= MAX_CODEX_TRANSLATOR_SCAN_DEPTH) { + continue; + } + + if (Array.isArray(item.value)) { + for (let index = item.value.length - 1; index >= 0; index -= 1) { + stack.push({ + value: item.value[index], + path: formatSettingsPathSegment(item.path, index), + depth: item.depth + 1, + }); + } + continue; + } + + const entries = Object.entries(item.value); + for (let index = entries.length - 1; index >= 0; index -= 1) { + const [key, child] = entries[index]; + stack.push({ + value: child, + path: formatSettingsPathSegment(item.path, key), + depth: item.depth + 1, + }); + } } - if (Array.isArray(value)) { - return value.flatMap((item, index) => - findCodexTranslatorUrlPaths(item, formatSettingsPathSegment(path, index)) - ); - } - - if (typeof value === 'object' && value !== null) { - return Object.entries(value).flatMap(([key, item]) => - findCodexTranslatorUrlPaths(item, formatSettingsPathSegment(path, key)) - ); - } - - return []; + return matches; } export function formatSettingsPathList(paths: string[]): string { diff --git a/tests/unit/commands/persist-command-handler.test.ts b/tests/unit/commands/persist-command-handler.test.ts index f80ad8c5..5179f505 100644 --- a/tests/unit/commands/persist-command-handler.test.ts +++ b/tests/unit/commands/persist-command-handler.test.ts @@ -415,6 +415,43 @@ describe('persist command Claude extension parity', () => { expect(renderedLogs).toContain('Native Codex target: ccsxp or ccs codex --target codex'); }); + it('does not fail after writing settings when a cleared env value is deeply nested', async () => { + await writeUnifiedConfig(); + + const settingsPath = path.join(tempRoot, '.claude', 'settings.json'); + await fs.promises.mkdir(path.dirname(settingsPath), { recursive: true }); + const deepValue = `${'{"nested":'.repeat(20000)}"leaf"${'}'.repeat(20000)}`; + await fs.promises.writeFile( + settingsPath, + `{"env":{"KEEP_ME":"still-here","ANTHROPIC_AUTH_TOKEN":${deepValue}}}\n`, + 'utf8' + ); + + const originalConsoleLog = console.log; + const capturedLogs: string[] = []; + console.log = (...args: unknown[]) => { + capturedLogs.push(args.map((arg) => String(arg)).join(' ')); + }; + + try { + await withScopedHome(() => handlePersistCommand(['default', '--yes'])); + } finally { + console.log = originalConsoleLog; + } + + const persisted = JSON.parse(await fs.promises.readFile(settingsPath, 'utf8')) as { + env: Record; + }; + + expect(persisted.env.KEEP_ME).toBe('still-here'); + expect(persisted.env.ANTHROPIC_AUTH_TOKEN).toBeUndefined(); + const renderedLogs = capturedLogs.join('\n'); + expect(renderedLogs).toContain("Profile 'default' written to"); + expect(renderedLogs).toContain('Config Receipt'); + expect(renderedLogs).toContain('Codex translator URL: not found'); + expect(renderedLogs).not.toContain('Failed to write settings'); + }); + it('warns in the persist receipt when a Codex translator URL remains in settings', async () => { await writeUnifiedConfig(); diff --git a/tests/unit/shared/stale-codex-translator-settings.test.ts b/tests/unit/shared/stale-codex-translator-settings.test.ts index be0cfd89..1873fb3d 100644 --- a/tests/unit/shared/stale-codex-translator-settings.test.ts +++ b/tests/unit/shared/stale-codex-translator-settings.test.ts @@ -31,4 +31,14 @@ describe('stale Codex translator settings scanner', () => { '["custom-key"][0]', ]); }); + + it('does not overflow the stack on deeply nested local settings values', () => { + let settings: Record = { value: 'leaf' }; + for (let depth = 0; depth < 20000; depth += 1) { + settings = { nested: settings }; + } + + expect(() => findCodexTranslatorUrlPaths(settings)).not.toThrow(); + expect(findCodexTranslatorUrlPaths(settings)).toEqual([]); + }); });