fix(persist): bound Codex translator receipt scan

This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-30 15:20:22 -04:00
committed by Tam Nhu Tran
parent 62a4d44b58
commit 5da6334736
3 changed files with 104 additions and 15 deletions
+57 -15
View File
@@ -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<object>();
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 {
@@ -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<string, string>;
};
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();
@@ -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<string, unknown> = { value: 'leaf' };
for (let depth = 0; depth < 20000; depth += 1) {
settings = { nested: settings };
}
expect(() => findCodexTranslatorUrlPaths(settings)).not.toThrow();
expect(findCodexTranslatorUrlPaths(settings)).toEqual([]);
});
});