diff --git a/src/main/codex/codex-hook-trust-grant.test.ts b/src/main/codex/codex-hook-trust-grant.test.ts index 5292be120c1..fdd5e27fd40 100644 --- a/src/main/codex/codex-hook-trust-grant.test.ts +++ b/src/main/codex/codex-hook-trust-grant.test.ts @@ -70,13 +70,16 @@ function managedEntry(eventLabel: CodexTrustEntry['eventLabel']): CodexTrustEntr } } -function buildPlan(entries: CodexTrustEntry[]): CodexManagedTrustGrantPlan { +function buildPlan( + entries: CodexTrustEntry[], + host: CodexManagedTrustGrantPlan['host'] = { kind: 'native' } +): CodexManagedTrustPlan { return { runtimeHomePath: runtimeHomeDir, tomlPath: join(runtimeHomeDir, 'config.toml'), managedCommand: MANAGED_COMMAND, managedEntries: entries, - host: { kind: 'native' }, + host, telemetryLane: 'real-home' } } @@ -270,6 +273,19 @@ describe('grantManagedCodexHookTrust', () => { expect(runner).toHaveBeenCalledTimes(2) }) + it('bounds transient cooldowns when host identities churn', async () => { + _internals.setGrantSessionRunner(() => { + throw new Error('spawn ETIMEDOUT') + }) + const entry = managedEntry('session_start') + for (let index = 0; index < 260; index += 1) { + await grantManagedCodexHookTrust( + buildPlan([entry], { kind: 'wsl', distro: `Distro-${index}`, linuxRuntimeHome: '/home/u' }) + ) + } + expect(_internals.transientCooldownCountForTests()).toBe(256) + }) + it('falls back on verify-failed without marking unsupported', async () => { const entries = [managedEntry('session_start')] const runner = vi.fn(async () => ({ diff --git a/src/main/codex/codex-hook-trust-grant.ts b/src/main/codex/codex-hook-trust-grant.ts index f886dbb6344..d4b2f0fa359 100644 --- a/src/main/codex/codex-hook-trust-grant.ts +++ b/src/main/codex/codex-hook-trust-grant.ts @@ -40,6 +40,7 @@ import { isCodexStateDbBackfillPending } from './codex-state-db' // Why: a transiently hung app-server must not block launch prep on every pane. // The legacy lane remains available while a short, host-scoped cooldown runs. export const CODEX_TRUST_GRANT_TRANSIENT_RETRY_INTERVAL_MS = 5 * 60_000 +const MAX_TRANSIENT_TRUST_COOLDOWNS = 256 /** * Ops escape hatch (not a setting): forces the unchanged fallback lane for the @@ -109,7 +110,15 @@ function fallback( } function startTransientCooldown(hostKey: CodexAppServerHostKey): void { + transientRetryAfterByHost.delete(hostKey) transientRetryAfterByHost.set(hostKey, Date.now() + CODEX_TRUST_GRANT_TRANSIENT_RETRY_INTERVAL_MS) + while (transientRetryAfterByHost.size > MAX_TRANSIENT_TRUST_COOLDOWNS) { + const oldest = transientRetryAfterByHost.keys().next().value + if (oldest === undefined) { + break + } + transientRetryAfterByHost.delete(oldest) + } } type GrantAttempt = { @@ -305,5 +314,8 @@ export const _internals = { diagnostics.verifyFailed = 0 diagnostics.lastFallbackReason = null transientRetryAfterByHost.clear() + }, + transientCooldownCountForTests(): number { + return transientRetryAfterByHost.size } }