diff --git a/scripts/check-dead-exports.test.ts b/scripts/check-dead-exports.test.ts index ec27ff8c2..91163e2da 100644 --- a/scripts/check-dead-exports.test.ts +++ b/scripts/check-dead-exports.test.ts @@ -19,6 +19,7 @@ import { loadGuardConfig, parseAllowlistText, parseGuardConfig, + parseInjectedScannedFiles, parseTsPruneLine, tsPruneSpawn, validateAllowlistEntry, @@ -28,6 +29,19 @@ import { const repoRoot = join(import.meta.dir, ".."); +// tsc --listFilesOnly costs ~1.5s per spawn and the count is deterministic +// for a clean tree. The floor tests and the end-to-end probe share one run +// through this cache; each caller computes it on first use, so no test +// depends on another having run. +let cachedScannedFileCount: number | undefined; +function liveScannedFileCount(): number { + cachedScannedFileCount ??= countScannedFiles( + repoRoot, + loadGuardConfig().tsconfig, + ); + return cachedScannedFileCount; +} + // A probe dead export in one of the scoped files must fail the guard: the // exact-name exemptions cover only the remaining deferred-cleanup flags // (the usage-formatter flags were removed with their exports by the CL-6815 @@ -310,6 +324,17 @@ describe("pinned scan invocation", () => { expect(() => parseGuardConfig(null)).toThrow(); expect(() => parseGuardConfig([])).toThrow(); }); + + test("the scanned-files injection accepts no override or a positive integer", () => { + expect(parseInjectedScannedFiles(undefined)).toBeUndefined(); + expect(parseInjectedScannedFiles("1217")).toBe(1217); + }); + + test("the scanned-files injection fails closed on a bogus value", () => { + for (const bad of ["", "0", "-5", "1.5", "abc"]) { + expect(() => parseInjectedScannedFiles(bad)).toThrow(); + } + }); }); describe("scan coverage floor", () => { @@ -321,14 +346,14 @@ describe("scan coverage floor", () => { test("the live program file count clears the checked-in floor", () => { const config = loadGuardConfig(); - const scanned = countScannedFiles(repoRoot, config.tsconfig); - expect(scanned).toBeGreaterThanOrEqual(config.minScannedFiles); + expect(liveScannedFileCount()).toBeGreaterThanOrEqual( + config.minScannedFiles, + ); }, 120_000); test("the checked-in floor stays tight to the live count", () => { const config = loadGuardConfig(); - const scanned = countScannedFiles(repoRoot, config.tsconfig); - expect(scanned).toBeLessThan(config.minScannedFiles * 1.1); + expect(liveScannedFileCount()).toBeLessThan(config.minScannedFiles * 1.1); }, 120_000); }); @@ -337,17 +362,50 @@ describe("scan coverage floor", () => { // scripts/ tree, the real guard runs as a subprocess, and the run must exit // nonzero naming the probe. The probe lives only for the test (never // committed) so the keep-alive check cannot itself become a dead export. +// +// The guard normally scans the full project via ts-prune (~5s); the probe's +// temp tsconfig includes only the probe file, so this run pays only ts-prune +// startup while still exercising the same wired-up gate (config load, +// allowlist validation, node-spawned ts-prune, output parsing, coverage +// floor, exit code). describe("violation end to end", () => { test("a temp dead export fails the guard, which names it", () => { const probeFile = `dead-export-guard-probe-${process.pid}.ts`; const probeName = `deadExportGuardProbe${process.pid}`; const probePath = join(repoRoot, "scripts", probeFile); + const narrowTsconfigFile = `dead-export-guard-tsconfig-${process.pid}.json`; + const narrowTsconfigPath = join(repoRoot, "scripts", narrowTsconfigFile); writeFileSync(probePath, `export const ${probeName} = 1;\n`); + writeFileSync( + narrowTsconfigPath, + JSON.stringify({ + compilerOptions: { + noEmit: true, + target: "ESNext", + module: "ESNext", + moduleResolution: "bundler", + skipLibCheck: true, + }, + // Relative to the narrow tsconfig's own directory (scripts/). + files: [probeFile], + }), + ); try { const ran = spawnSync( process.execPath, ["scripts/check-dead-exports.ts"], - { cwd: repoRoot, encoding: "utf8" }, + { + cwd: repoRoot, + encoding: "utf8", + // The file-count subprocess is exercised by the floor tests above; + // inject the same deterministic value so this run pays only + // ts-prune. + env: { + ...process.env, + DEAD_EXPORT_GUARD_SCANNED_FILES: String(liveScannedFileCount()), + DEAD_EXPORT_GUARD_TSCONFIG: `scripts/${narrowTsconfigFile}`, + }, + }, ); expect(ran.status).toBe(1); expect(`${ran.stdout}\n${ran.stderr}`).toContain( @@ -355,6 +413,7 @@ describe("violation end to end", () => { ); } finally { rmSync(probePath, { force: true }); + rmSync(narrowTsconfigPath, { force: true }); } }, 120_000); }); diff --git a/scripts/check-dead-exports.ts b/scripts/check-dead-exports.ts index 6798b0d44..244066457 100644 --- a/scripts/check-dead-exports.ts +++ b/scripts/check-dead-exports.ts @@ -19,6 +19,18 @@ import { fileURLToPath } from "node:url"; // gate fails closed when the scanned file count drops below that config's // floor. +// Test injection (same pattern as teardownDeadlineMs / maxScanBytes): a +// subprocess test that needs the real gate without paying for the ~1.5s tsc +// file-count run can pass the deterministic count itself. Production never +// sets it, so the gate always computes the count in normal runs. +const scannedFilesEnv = "DEAD_EXPORT_GUARD_SCANNED_FILES"; + +// Test injection (same pattern as scannedFilesEnv): a subprocess test that +// needs the wired-up gate without paying for the ~5s full-project ts-prune +// run can point the pinned scan at a smaller tsconfig. Production never sets +// it, so the gate always scans the pinned tsconfig in normal runs. +const tsconfigEnv = "DEAD_EXPORT_GUARD_TSCONFIG"; + const here = dirname(fileURLToPath(import.meta.url)); const repoRoot = dirname(here); const allowlistPath = join(here, "dead-export-allowlist.txt"); @@ -284,6 +296,22 @@ export function tsPruneSpawn( return { command: "node", args: [tsPruneBinPath, ...tsPruneArgs] }; } +// Parses the test-injection env override. Undefined means compute the count +// as usual; anything else must be a positive integer or the gate fails +// closed rather than trusting a bogus number. +export function parseInjectedScannedFiles( + value: string | undefined, +): number | undefined { + if (value === undefined) return undefined; + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed <= 0) { + throw new Error( + `${scannedFilesEnv} must be a positive integer when set (test injection only)`, + ); + } + return parsed; +} + // Counts the TypeScript files the pinned tsconfig pulls into its program via // tsc --listFilesOnly: the same project ts-prune analyzes. A narrowed // tsconfig (or a moved scan root) shrinks this count, and the gate fails @@ -329,6 +357,19 @@ function main(): void { } catch (err) { fail(`dead-export guard: invalid guard config: ${(err as Error).message}`); } + const overrideTsconfig = process.env[tsconfigEnv]; + if (overrideTsconfig !== undefined) { + if (!existsSync(join(repoRoot, overrideTsconfig))) { + fail( + `dead-export guard: tsconfig override not found: ${overrideTsconfig}`, + ); + } + config = { + ...config, + tsconfig: overrideTsconfig, + tsPruneArgs: ["-p", overrideTsconfig], + }; + } const allowlistText = readFileSync(allowlistPath, "utf8"); const allowlistProblems = [ ...validateAllowlistText(allowlistText), @@ -356,12 +397,17 @@ function main(): void { } const outcome = evaluateGuard(rules, String(pruned.stdout)); let scannedFiles: number; - try { - scannedFiles = countScannedFiles(repoRoot, config.tsconfig); - } catch (err) { - fail( - `dead-export guard: could not count scanned files: ${(err as Error).message}`, - ); + const injectedCount = parseInjectedScannedFiles(process.env[scannedFilesEnv]); + if (injectedCount !== undefined) { + scannedFiles = injectedCount; + } else { + try { + scannedFiles = countScannedFiles(repoRoot, config.tsconfig); + } catch (err) { + fail( + `dead-export guard: could not count scanned files: ${(err as Error).message}`, + ); + } } console.log( `dead-export guard: ${outcome.dead} consumer-less exports, ${outcome.dead - outcome.violations.length} allowlisted, ${outcome.violations.length} violations, ${scannedFiles} scanned files (floor ${config.minScannedFiles})`, diff --git a/scripts/ci-timings.json b/scripts/ci-timings.json index c4991aae5..e95a24556 100644 --- a/scripts/ci-timings.json +++ b/scripts/ci-timings.json @@ -1,442 +1,367 @@ { "version": 1, "files": { - "src/subagent/run-shell-child-reap.test.ts": 30168, - "scripts/check-dead-exports.test.ts": 16485, - "src/plugins/read-file-guard-plugin.test.ts": 14113, - "src/permission/permission.test.ts": 8597, - "src/tui/runtime-channels.test.ts": 4434, - "src/shell/background-shell.test.ts": 4104, - "e2e/exec-signal-finalize.test.ts": 4103, - "e2e/compaction-baseline.test.ts": 3555, - "src/permission/gate.test.ts": 3468, - "src/plugins/shell-guard-plugin.test.ts": 3191, - "e2e/exec-shutdown-reap.test.ts": 2584, - "src/subagent/spawn-agent-worktree.test.ts": 2515, - "e2e/crash-finalize.test.ts": 2196, - "src/tui/url-click.test.ts": 2103, - "scripts/test-parallel.test.ts": 2093, - "src/session/list-sessions.test.ts": 1943, - "src/auth/codex/refresh-lock.test.ts": 1896, - "src/tui/tool-execution-watchdog.test.ts": 1877, - "src/tui/markdown-rows.test.ts": 1753, - "e2e/signal-finalize.test.ts": 1672, - "src/agent/exa-web-fetch-alias.test.ts": 1521, - "src/agent/environment.test.ts": 1504, - "src/agent/tools-mcp-disconnect.test.ts": 1492, - "scripts/prepare-homebrew-tap-release.test.ts": 1479, - "e2e/subagent-permission.test.ts": 1313, - "src/tui/provider-setup.test.ts": 1313, - "e2e/git-push-scoped.test.ts": 1295, - "src/tui/image-attachments.test.ts": 1286, - "src/auth/store.test.ts": 1233, - "src/tui/product-host.test.ts": 1187, - "src/tui/command-surfaces.test.ts": 1147, - "src/tui/transcript-long-log-scroll.test.ts": 1135, - "src/auth/codex/session-refresh-race.test.ts": 1017, - "src/subagent/agent-fleet.test.ts": 948, - "src/session/session-dir.test.ts": 947, - "scripts/verify-corbits-only-scope.test.ts": 799, - "src/tui/mention-resolution.test.ts": 797, - "src/session/project-key.test.ts": 772, - "src/workflows/host.test.ts": 746, - "src/config.test.ts": 705, - "src/tui/shell.test.ts": 697, - "src/permission/reactor-authorize.test.ts": 693, - "e2e/reactor-approval-suspend.test.ts": 684, - "src/plugins/permission-plugin.test.ts": 676, - "src/tui/slash-popup-gate.test.ts": 676, - "src/permission/project-approvals-trust.test.ts": 670, - "e2e/compaction-read-paging.test.ts": 653, - "src/permission/classify-security.test.ts": 651, - "src/trust/path-plugin-trust.test.ts": 638, - "e2e/credential-recovery.test.ts": 625, - "src/mcp/plugin.test.ts": 622, - "src/session/hooks.test.ts": 611, - "src/session/run-liveness.test.ts": 578, - "src/exec/runner.test.ts": 576, - "src/state.test.ts": 548, - "src/session/optimized-context-store.test.ts": 544, - "src/tui/runtime-bridge.test.ts": 544, - "src/tui/wave6.test.ts": 535, - "src/tui/gate-wire.test.ts": 519, - "src/permission/worktree-roots.test.ts": 500, - "src/tui/landing.test.ts": 500, - "e2e/mcp-late-dispatch.test.ts": 482, - "src/tui/transcript-anchor.test.ts": 462, - "e2e/rawmode-sigint.test.ts": 455, - "testkit/temporary-git-repo.test.ts": 448, - "src/tools/web-fetch.test.ts": 445, - "src/subagent/run-resolved-provider-failure.test.ts": 440, - "e2e/subagent-orchestration.test.ts": 434, - "src/mcp/auth-store.test.ts": 406, - "src/tui/keybindings.test.ts": 390, - "src/tui/prompt-slash-exit.test.ts": 384, - "src/tui/palette-paint.test.ts": 382, - "e2e/reactor-permission-multi-turn.test.ts": 377, - "src/permission/approval-store-migration.test.ts": 373, - "src/session/cache-ttl-resume.test.ts": 365, - "src/tui/transcript-layout.test.ts": 364, - "scripts/eval-capability.test.ts": 362, - "src/tui/allow-once-reprompt.test.ts": 359, - "src/agent/background-shell-tool.test.ts": 351, - "src/tui/runner-host.test.ts": 343, - "src/tui/turn-monitor.test.ts": 323, - "src/subagent/run-skill-scope.test.ts": 320, - "scripts/oxlint-no-bare-mock-module.test.ts": 310, - "src/tui/list-modal.test.ts": 308, - "src/perf/permission-subagent-spans.test.ts": 300, - "src/session/sent-messages.test.ts": 299, - "src/tui/agent-ask-wake.test.ts": 298, - "src/session/run-state-snapshots.test.ts": 286, - "src/plugins/secret-guard-config-denylist.test.ts": 282, - "src/permission/grant-scope.test.ts": 268, - "src/tui/request-approval.test.ts": 258, - "src/subagent/run-persist-close.test.ts": 257, - "src/session/runtime-assembly.test.ts": 244, - "src/agent/posix-tool-plugins.test.ts": 243, - "src/tui/prompt-features.test.ts": 235, - "src/permission/queue.test.ts": 233, - "src/tui/overlay-paint.test.ts": 212, - "src/tui/overlays.test.ts": 195, - "src/telemetry/toggle.test.ts": 190, - "src/tui/gutter-labels.test.ts": 177, - "e2e/subagent-recoverable-failure.test.ts": 170, - "src/workflows/state.test.ts": 165, - "src/plugins/secret-guard-symlink.test.ts": 163, - "src/subagent/run-authority.test.ts": 158, - "src/tui/mention-popup.test.ts": 149, - "src/tui/ramp-paint.test.ts": 149, - "e2e/harness-smoke.test.ts": 145, - "src/session/rename-session.test.ts": 142, - "src/session/state.test.ts": 142, - "src/tui/prompt-box.test.ts": 142, - "src/tui/runner/exit.test.ts": 140, - "src/plugins/ripgrep-plugin.test.ts": 138, - "src/subagent/index.test.ts": 137, - "scripts/eval-completion.test.ts": 127, - "src/tui/observe-live.test.ts": 124, - "src/telemetry/index.test.ts": 120, - "src/tui/focus-routing.test.ts": 119, + "scripts/check-dead-exports.test.ts": 1769, + "scripts/test-parallel.test.ts": 2400, + "src/plugins/shell-guard-plugin.test.ts": 1883, + "src/tui/tool-execution-watchdog.test.ts": 1853, + "src/auth/codex/refresh-lock.test.ts": 930, + "src/tui/provider-setup.test.ts": 1562, + "src/tui/url-click.test.ts": 1442, + "e2e/exec-signal-finalize.test.ts": 1299, + "src/tui/product-host.test.ts": 1296, + "e2e/exec-shutdown-reap.test.ts": 1175, + "src/agent/tools-mcp-disconnect.test.ts": 1167, + "src/subagent/run-shell-child-reap.test.ts": 850, + "src/auth/codex/session-refresh-race.test.ts": 460, + "e2e/crash-finalize.test.ts": 892, + "e2e/compaction-baseline.test.ts": 836, + "src/subagent/agent-fleet.test.ts": 806, + "src/auth/store.test.ts": 778, + "src/tui/transcript-long-log-scroll.test.ts": 754, + "src/tui/markdown-rows.test.ts": 664, + "src/tui/slash-popup-gate.test.ts": 565, + "src/tui/syntax-highlight.test.ts": 550, + "src/tui/provider-remove.test.ts": 549, + "src/tui/command-surfaces.test.ts": 480, + "src/shell/background-shell.test.ts": 451, + "src/exec/runner.test.ts": 442, + "src/tui/gate-wire.test.ts": 370, + "src/mcp/auth-store.test.ts": 361, + "src/tools/web-fetch.test.ts": 337, + "e2e/rawmode-sigint.test.ts": 318, + "src/plugins/secret-guard-plugin.test.ts": 318, + "src/tui/image-attachments.test.ts": 302, + "src/tui/keybindings.test.ts": 293, + "src/tui/prompt-slash-exit.test.ts": 275, + "src/permission/permission.test.ts": 261, + "e2e/signal-finalize.test.ts": 259, + "src/tui/request-approval.test.ts": 259, + "src/tui/wave6.test.ts": 251, + "src/agent/background-shell-tool.test.ts": 230, + "src/tui/runtime-bridge.test.ts": 229, + "e2e/subagent-permission.test.ts": 228, + "src/tui/landing.test.ts": 219, + "scripts/oxlint-no-bare-mock-module.test.ts": 213, + "scripts/eval-capability.test.ts": 199, + "src/tui/transcript-anchor.test.ts": 189, + "src/tui/runner/settings-theme.test.ts": 179, + "src/telemetry/toggle.test.ts": 176, + "src/tui/shell.test.ts": 168, + "src/tui/allow-once-reprompt.test.ts": 165, + "src/subagent/spawn-agent-worktree.test.ts": 160, + "e2e/reactor-approval-suspend.test.ts": 143, + "src/subagent/run-persist-close.test.ts": 143, + "src/subagent/index.test.ts": 132, + "src/session/run-liveness.test.ts": 129, + "src/tui/transcript-layout.test.ts": 119, + "src/tui/agent-ask-wake.test.ts": 118, + "src/tui/overlays.test.ts": 117, + "src/auth/codex/callback-server.test.ts": 113, "src/director.test.ts": 113, - "src/tui/runner/wiring.ask-wake.test.ts": 113, - "src/subagent/followup-live-agent.test.ts": 109, - "src/tui/row-click.test.ts": 107, - "src/telemetry/product-events.test.ts": 106, - "src/tui/prompt-chrome.test.ts": 100, - "src/session/runtime-assembly-migration.test.ts": 98, - "src/session/resume-interrupted.test.ts": 96, - "src/subagent/run-recoverable-failure.test.ts": 95, - "src/session/stream-journal.test.ts": 94, - "src/tui/runner/wiring.stall-bound.test.ts": 94, - "src/tui/approval-prompt-visibility.test.ts": 93, - "src/session/session-label.test.ts": 92, - "src/subagent/session-store.test.ts": 92, - "src/permission/critique-grep-file-env.test.ts": 85, - "src/list-dir.test.ts": 84, - "src/permission/path-restriction.test.ts": 83, - "src/index.test.ts": 79, - "src/mcp/oauth-provider.test.ts": 79, - "src/tui/queued-delivery-hop.test.ts": 78, - "src/permission/approval-log.test.ts": 75, - "src/permission/workspace-containment.test.ts": 74, - "src/tui/tool-rows.test.ts": 74, - "e2e/vendored-carry.test.ts": 70, - "src/settings.test.ts": 70, - "src/agent/tools.test.ts": 68, - "src/config/settings.test.ts": 67, - "src/tools/web-search.test.ts": 66, - "src/tui/plugin-diagnostics-sink.test.ts": 66, - "e2e/compaction-atomicity.test.ts": 64, - "scripts/generate-homebrew-tap.test.ts": 64, - "src/mcp/client-auth-reauth-cap.test.ts": 64, - "src/plugins/delete-file-plugin.test.ts": 64, - "src/subagent/run-settlement.test.ts": 64, - "src/trust/project-trust.test.ts": 64, - "src/agent/tool-search.test.ts": 63, - "src/session/incremental-jsonl.test.ts": 63, - "src/subagent/nudge-director.test.ts": 63, - "src/tui/commands/built-in.test.ts": 62, - "src/tui/components/at-mention/list.test.ts": 58, - "src/tui/overlay-overflow.test.ts": 58, - "src/agent/fleet-verbs-mount.test.ts": 57, - "src/mcp/add-server.test.ts": 56, - "src/session/live-model-switch.test.ts": 55, - "src/subagent/retain-salvage.test.ts": 54, - "src/tui/margins.test.ts": 54, - "src/plugins/secret-guard-plugin.test.ts": 53, - "src/tui/row-update-perf.test.ts": 51, - "src/tui/steer-worker-invariant.test.ts": 51, - "src/tui/welcome.test.ts": 50, - "e2e/reactor-events-guards.test.ts": 49, - "src/plugins/verify-plugin.test.ts": 48, - "src/tui/wave7.test.ts": 48, - "src/subagent/intervention-log.test.ts": 47, - "src/trust/path-trust.test.ts": 47, - "src/shell/run-shell-authz.test.ts": 45, - "src/tui/approval-delivery.test.ts": 44, - "src/tui/transcript-panels.test.ts": 44, - "src/mcp/callback-server.test.ts": 43, + "src/tui/runner/exit.test.ts": 112, + "e2e/compaction-read-paging.test.ts": 108, + "src/agent/codex-tool-mount.test.ts": 108, + "src/plugins/read-file-guard-plugin.test.ts": 107, + "src/tui/turn-monitor.test.ts": 104, + "e2e/reactor-permission-multi-turn.test.ts": 103, + "scripts/eval-completion.test.ts": 101, + "src/telemetry/index.test.ts": 101, + "src/session/list-sessions.test.ts": 100, + "src/session/optimized-context-store.test.ts": 99, + "src/tui/row-click.test.ts": 96, + "src/session/stream-journal.test.ts": 95, + "src/tui/focus-routing.test.ts": 89, + "src/tui/palette-paint.test.ts": 88, + "src/subagent/spawn-cold-start.test.ts": 84, + "src/session/hooks.test.ts": 82, + "src/permission/gate.test.ts": 81, + "src/session/cache-ttl-resume.test.ts": 81, + "src/tui/observe-live.test.ts": 78, + "src/subagent/session-store.test.ts": 75, + "src/tui/prompt-features.test.ts": 73, + "src/tui/list-modal.test.ts": 67, + "e2e/git-push-scoped.test.ts": 64, + "src/subagent/run-requires-tools.test.ts": 61, + "src/subagent/run-skill-scope.test.ts": 61, + "src/tui/mention-popup.test.ts": 61, + "src/mcp/client-auth-reauth-cap.test.ts": 59, + "src/auth/xai/callback-server.test.ts": 58, + "src/tui/runner-host.test.ts": 58, + "e2e/mcp-late-dispatch.test.ts": 57, + "e2e/subagent-recoverable-failure.test.ts": 56, + "src/agent/tool-search.test.ts": 56, + "src/subagent/retain-salvage.test.ts": 56, + "src/tui/runtime-channels.test.ts": 56, + "src/config.test.ts": 53, + "src/subagent/run-recoverable-failure.test.ts": 52, + "src/tui/runner/wiring.ask-wake.test.ts": 52, + "e2e/subagent-orchestration.test.ts": 51, + "src/subagent/followup-live-agent.test.ts": 48, + "src/tui/overlay-paint.test.ts": 48, + "src/session/state.test.ts": 47, + "src/subagent/run-resolved-provider-failure.test.ts": 47, + "src/tui/prompt-box.test.ts": 47, + "src/tui/tool-rows.test.ts": 47, + "src/workflows/host.test.ts": 47, + "src/tui/prompt-chrome.test.ts": 46, + "src/tui/approval-prompt-visibility.test.ts": 44, + "src/tui/queued-delivery-hop.test.ts": 44, "src/session/active-host.test.ts": 43, - "src/tui/overlay-key-routing.test.ts": 43, - "scripts/vendor-patch-ledger.test.ts": 41, - "src/session/approval-resume-interrupt.test.ts": 41, - "src/tui/copy-wire.test.ts": 40, - "src/tui/diff-rows.test.ts": 40, - "src/agent/prompt-sizes.test.ts": 39, - "src/session/compaction-archive.test.ts": 39, - "src/tui/mcp-view.test.ts": 39, - "src/tui/runtime-shutdown.test.ts": 39, - "e2e/reactor-empty-turn.test.ts": 38, - "src/tui/overlay-empty-state.test.ts": 37, - "src/tui/overlay-primary-state.test.ts": 36, - "src/tui/reasoning-fold.test.ts": 36, - "src/subagent/trace-reader.test.ts": 35, - "src/auth/codex/callback-server.test.ts": 34, - "src/permission/shell-argv-authorize.test.ts": 34, - "src/plugins/change-diff.test.ts": 34, - "src/plugins/data-only-agent.test.ts": 34, - "src/tui/collapse.test.ts": 34, - "src/tui/provider-setup-submit.test.ts": 34, - "src/tui/run-snapshot-kind.test.ts": 34, - "src/permission/cross-commit-composition.test.ts": 33, - "src/permission/store.test.ts": 33, - "e2e/reactor-approval-acceptance.test.ts": 32, - "src/context-compactor.test.ts": 32, - "src/subagent/lifecycle-tools.test.ts": 32, - "src/perf/index.test.ts": 30, - "src/plugins/bounded-grep-fallback.test.ts": 30, - "src/subagent/run-submit-result-rotation.test.ts": 30, - "src/tui/render-loop.test.ts": 30, - "src/tui/thinking-reveal.test.ts": 30, - "src/auth/xai/session-refresh-race.test.ts": 29, - "src/pricing-metadata.test.ts": 29, - "src/subagent/run-ask-director-continue.test.ts": 29, - "src/provider/validate-connection.test.ts": 28, - "src/tui/overlay-body.test.ts": 28, - "src/tui/prompt-highlight.test.ts": 28, - "src/auth/codex/session.test.ts": 27, - "src/tui/description-zone.test.ts": 27, - "src/tui/onboarding.test.ts": 27, - "src/exec/dispose.test.ts": 26, - "src/plugins/claude-plugins.test.ts": 26, - "src/tui/chrome-repaint.test.ts": 26, - "src/plugins/repo-locator.test.ts": 25, - "src/plugins/data-only-commands.test.ts": 24, - "src/subagent/run-audit-store.test.ts": 24, - "src/tui/provider-connect.test.ts": 24, - "src/agent/use-skill.test.ts": 23, - "src/plugins/secret-guard-shell-symlink.test.ts": 22, - "src/tui/markdown-parser.test.ts": 22, - "src/workflows/runtime-persistence.test.ts": 22, + "src/subagent/run-settlement.test.ts": 43, + "src/plugins/secret-guard-config-denylist.test.ts": 42, + "src/subagent/intervention-log.test.ts": 42, + "src/permission/classify-security.test.ts": 37, + "src/tui/approval-delivery.test.ts": 36, + "src/tui/gutter-labels.test.ts": 36, + "src/subagent/run-authority.test.ts": 34, + "src/tui/copy-wire.test.ts": 33, + "src/tui/overlay-overflow.test.ts": 33, + "src/settings.test.ts": 32, + "src/tui/diff-rows.test.ts": 31, + "src/plugins/permission-plugin.test.ts": 30, + "src/tui/welcome.test.ts": 30, + "src/agent/environment.test.ts": 29, + "src/plugins/ripgrep-plugin.test.ts": 28, + "src/session/project-key.test.ts": 28, + "src/provider/validate-connection.test.ts": 26, + "src/tui/runner/wiring.stall-bound.test.ts": 26, + "src/permission/approval-store-migration.test.ts": 25, + "src/tui/steer-worker-invariant.test.ts": 25, + "scripts/prepare-homebrew-tap-release.test.ts": 24, + "scripts/verify-corbits-only-scope.test.ts": 24, + "src/agent/exa-web-fetch-alias.test.ts": 24, + "src/agent/posix-tool-plugins.test.ts": 24, + "src/mcp/client-telemetry.test.ts": 24, + "src/mcp/plugin.test.ts": 24, + "src/tui/overlay-primary-state.test.ts": 24, + "src/plugins/verify-plugin.test.ts": 23, + "src/session/runtime-assembly.test.ts": 23, + "src/tui/prompt-highlight.test.ts": 23, + "src/exec/dispose.test.ts": 22, + "src/permission/reactor-authorize.test.ts": 22, + "src/plugins/change-diff.test.ts": 22, + "src/tui/runtime-shutdown.test.ts": 22, "src/agent/directors/skywalker/package.test.ts": 21, - "src/auth/xai/callback-server.test.ts": 21, - "src/extensions/skills.test.ts": 21, - "src/tui/runner/wiring.skip-permissions-warning.test.ts": 21, - "src/tui/startup-transcript.test.ts": 21, - "src/tui/mcp-copy-failure.test.ts": 20, - "src/plugins/marketplace.test.ts": 19, - "src/config/oauth-stores-race.test.ts": 18, - "src/director-workflows.test.ts": 18, - "src/plugins/loader.test.ts": 18, - "src/tui/overlay-float-reset.test.ts": 18, - "src/tui/runner/wiring.stall-poll.test.ts": 18, - "src/plugins/evidence-archive-search-plugin.test.ts": 17, - "src/session/compaction-verify.test.ts": 17, - "src/shell/literal-path-arguments.test.ts": 17, - "src/plugins/edit-file-diagnostics-plugin.test.ts": 16, - "src/tui/teardown.test.ts": 16, - "src/tui/width-contract.test.ts": 16, - "src/permission/saved-skip-warning.test.ts": 15, + "src/permission/worker-grant-flow.test.ts": 21, + "src/pricing-metadata.test.ts": 21, + "src/tui/row-update-perf.test.ts": 21, + "src/permission/project-approvals-trust.test.ts": 20, + "src/state.test.ts": 20, + "src/tui/wave7.test.ts": 20, + "src/session/session-dir.test.ts": 19, + "e2e/compaction-atomicity.test.ts": 18, + "e2e/credential-recovery.test.ts": 18, + "src/tui/collapse.test.ts": 18, + "src/tui/margins.test.ts": 18, + "src/tui/ramp-paint.test.ts": 18, + "src/tui/transcript-panels.test.ts": 18, + "src/tui/overlay-empty-state.test.ts": 17, + "src/agent/director.test.ts": 16, + "src/mcp/oauth-provider.test.ts": 16, + "e2e/harness-smoke.test.ts": 15, + "src/permission/grant-scope.test.ts": 15, "src/plugins/lexicon-skill.test.ts": 15, - "src/shell/persistent-shell-cwd.test.ts": 15, - "src/tui/overlay-min-geometry.test.ts": 15, - "scripts/check-gate.test.ts": 14, - "src/plugins/path-escape-plugin.test.ts": 14, - "src/plugins/result-truncation-plugin.test.ts": 14, - "src/plugins/skill-commands.test.ts": 14, - "src/tui/log-sink.test.ts": 14, - "src/tui/overlay-fixture-fallback.test.ts": 14, - "src/tui/runtime-bridge-coalesce.test.ts": 14, - "src/inference-gateway-error.test.ts": 13, - "src/perf/attribution-report.test.ts": 13, - "src/perf/rollup.test.ts": 13, - "src/plugins/secret-guard-credential-surface.test.ts": 13, - "src/plugins/uninstall.test.ts": 13, - "src/session/approval-resume.test.ts": 13, - "src/agent/director.test.ts": 12, - "src/auth/codex/session-failure.test.ts": 12, - "src/plugins/rg-run.test.ts": 12, - "src/profiles.test.ts": 12, - "src/tui/correlation-acceptance.test.ts": 12, - "src/auth/codex/auth.test.ts": 11, - "src/auth/xai/session.test.ts": 11, - "src/changelog/index.test.ts": 11, - "src/mcp/client-unwrap.test.ts": 11, - "src/provider/model-catalogs.test.ts": 11, - "src/session/assemble-runtime.test.ts": 11, - "src/subagent/run-source.test.ts": 11, - "src/tui/overlay-body-cache-staleness.test.ts": 11, - "src/tui/overlay-view.test.ts": 11, - "src/tui/workspace-watch.test.ts": 11, - "evals/capability/lib.test.ts": 10, - "evals/compaction/metrics.test.ts": 10, + "src/session/run-state-snapshots.test.ts": 15, + "src/session/sent-messages.test.ts": 15, + "src/subagent/lifecycle-tools.test.ts": 15, + "src/trust/path-plugin-trust.test.ts": 15, + "testkit/temporary-git-repo.test.ts": 15, + "src/auth/xai/session-refresh-race.test.ts": 14, + "src/mcp/callback-server.test.ts": 14, + "src/session/compaction-archive.test.ts": 14, + "src/tui/mention-resolution.test.ts": 14, + "src/tui/overlay-key-routing.test.ts": 14, + "e2e/vendored-carry.test.ts": 13, + "src/session/rename-session.test.ts": 13, + "src/tui/overlay-body.test.ts": 13, + "src/permission/shell-argv-authorize.test.ts": 12, + "src/permission/worker-grant.test.ts": 12, + "src/session/approval-resume-interrupt.test.ts": 12, + "src/tui/chrome-repaint.test.ts": 12, + "src/tui/mcp-view.test.ts": 12, + "evals/completion/lib.test.ts": 11, + "src/mcp/add-server.test.ts": 11, + "src/plugins/secret-guard-symlink.test.ts": 11, + "src/subagent/trace-reader.test.ts": 11, + "src/tui/correlation-acceptance.test.ts": 11, + "src/tui/description-zone.test.ts": 11, + "src/tui/onboarding.test.ts": 11, + "src/tui/startup-transcript.test.ts": 11, + "src/workflows/state.test.ts": 11, "src/plugins/corbits-skills-catalog.test.ts": 10, - "src/session/anthropic-cache-prompt.test.ts": 10, - "src/session/compactor-pairing.test.ts": 10, - "src/tui/decision-truncation.test.ts": 10, - "src/tui/view/lines.test.ts": 10, - "src/agent/codex-tool-mount.test.ts": 9, - "src/agent/compaction.test.ts": 9, - "src/agent/directors/attached-skills.test.ts": 9, - "src/agent/retry-policy.test.ts": 9, - "src/tui/copy-path.test.ts": 9, - "evals/completion/lib.test.ts": 8, + "src/subagent/run-ask-director-continue.test.ts": 10, + "src/telemetry/product-events.test.ts": 10, + "src/tui/history-hydrate.test.ts": 10, + "src/tui/reasoning-fold.test.ts": 10, + "src/tui/render-loop.test.ts": 10, + "e2e/reactor-events-guards.test.ts": 9, + "src/context-compactor.test.ts": 9, + "src/perf/permission-subagent-spans.test.ts": 9, + "src/permission/approval-log.test.ts": 9, + "src/session/approval-resume.test.ts": 9, + "src/shell/run-shell-authz.test.ts": 9, + "src/trust/project-trust.test.ts": 9, + "src/tui/overlay-min-geometry.test.ts": 9, "src/agent/prompts.test.ts": 8, - "src/plugins/project-plugin-trust.test.ts": 8, - "src/provider/grok-responses.test.ts": 8, - "src/session/compaction-handoff.test.ts": 8, - "src/subagent/trace-tool.test.ts": 8, - "src/tui/mark-anim.test.ts": 8, - "src/tui/mcp-reconnect-surface.test.ts": 8, - "src/tui/prompt-attachments.test.ts": 8, - "src/tui/row-retext.test.ts": 8, - "src/tui/stream.test.ts": 8, - "src/tui/syntax-highlight.test.ts": 8, + "src/config/settings.test.ts": 8, + "src/permission/cross-commit-composition.test.ts": 8, + "src/permission/path-restriction.test.ts": 8, + "src/plugins/data-only-agent.test.ts": 8, + "src/plugins/search-files-fail-closed.test.ts": 8, + "src/plugins/secret-guard-shell-symlink.test.ts": 8, + "src/shell/literal-path-arguments.test.ts": 8, + "src/tui/overlay-float-reset.test.ts": 8, + "src/tui/runner/wiring.stall-poll.test.ts": 8, + "src/tui/thinking-reveal.test.ts": 8, + "e2e/reactor-approval-acceptance.test.ts": 7, + "e2e/reactor-empty-turn.test.ts": 7, "src/agent/directors/testsmith/package.test.ts": 7, - "src/mcp/client-auth-policy.test.ts": 7, - "src/permission/auto-shell-policy.test.ts": 7, - "src/plugins/tool-output-uri-plugin.test.ts": 7, - "src/pricing-fetcher.test.ts": 7, - "src/provider/codex-responses-sse.test.ts": 7, - "src/subagent/submit-result.test.ts": 7, + "src/agent/prompt-sizes.test.ts": 7, + "src/agent/retry-policy.test.ts": 7, + "src/agent/tools.test.ts": 7, + "src/permission/queue.test.ts": 7, + "src/permission/workspace-containment.test.ts": 7, + "src/plugins/project-plugin-trust.test.ts": 7, + "src/subagent/nudge-director.test.ts": 7, + "src/tui/commands/built-in.test.ts": 7, "src/tui/mouse-reporting-disabled.test.ts": 7, - "src/tui/overlay-reshape-selection.test.ts": 7, - "src/tui/queued-delivery.test.ts": 7, - "src/tui/tool-formatter.test.ts": 7, - "evals/capability/behaviors.test.ts": 6, + "src/tui/overlay-fixture-fallback.test.ts": 7, + "src/tui/provider-setup-submit.test.ts": 7, + "src/tui/runtime-bridge-coalesce.test.ts": 7, "evals/capability/locked-fixtures.test.ts": 6, - "src/config/onboarded-persistence.test.ts": 6, - "src/provider/replay-sanitizer.test.ts": 6, - "src/session/commit-signer.test.ts": 6, - "src/subagent/fleet-report.test.ts": 6, - "src/tui/command-display.test.ts": 6, - "src/tui/deliver-agent-message.test.ts": 6, - "src/tui/diff.test.ts": 6, - "src/tui/overlay-list.test.ts": 6, - "src/tui/turn-state.test.ts": 6, - "src/agent/lsp-availability.test.ts": 5, - "src/agent/skill-search.test.ts": 5, - "src/config/inference-sources.test.ts": 5, - "src/config/providers.test.ts": 5, - "src/plugins/edit-file-line-range.test.ts": 5, + "scripts/vendor-patch-ledger.test.ts": 6, + "src/config/custom-reasoning.test.ts": 6, + "src/plugins/claude-plugins.test.ts": 6, + "src/plugins/result-truncation-plugin.test.ts": 6, + "src/session/compaction-verify.test.ts": 6, + "src/session/incremental-jsonl.test.ts": 6, + "src/session/resume-interrupted.test.ts": 6, + "src/tui/components/at-mention/list.test.ts": 6, "src/plugins/example-agent-plugin.test.ts": 5, - "src/prompts.test.ts": 5, - "src/provider/codex-content-type-repair.test.ts": 5, - "src/provider/codex-responses.test.ts": 5, - "src/provider/openai-compatible-adapter.test.ts": 5, - "src/session/summarizer.test.ts": 5, - "src/subagent/fleet-dry-drive.test.ts": 5, - "src/tui/command-catalog.test.ts": 5, - "src/tui/geometry.test.ts": 5, - "src/agent/context-extensions.test.ts": 4, - "src/agent/director-compaction.test.ts": 4, - "src/auth/credential-surface-coverage.test.ts": 4, - "src/crash/report.test.ts": 4, - "src/perf/otel-sink.test.ts": 4, - "src/permission/command.test.ts": 4, - "src/plugins/agent-plugins.test.ts": 4, - "src/session/compaction-lifecycle.test.ts": 4, - "src/session/stream-consumer.test.ts": 4, + "src/provider/opencode-go-adapter.test.ts": 5, + "src/session/assemble-runtime.test.ts": 5, + "src/session/compaction-handoff.test.ts": 5, + "src/session/runtime-assembly-migration.test.ts": 5, + "src/session/session-label.test.ts": 5, + "src/subagent/run-ask-director-grant.test.ts": 5, + "src/trust/path-trust.test.ts": 5, + "src/tui/mcp-reconnect-surface.test.ts": 5, + "src/tui/overlay-body-cache-staleness.test.ts": 5, + "src/tui/provider-connect.test.ts": 5, + "src/tui/view/registry.test.ts": 5, + "evals/capability/lib.test.ts": 4, + "src/agent/compaction.test.ts": 4, + "src/agent/fleet-verbs-mount.test.ts": 4, + "src/index.test.ts": 4, + "src/permission/auto-shell-policy.test.ts": 4, + "src/permission/store.test.ts": 4, + "src/plugins/delete-file-plugin.test.ts": 4, + "src/plugins/evidence-archive-search-plugin.test.ts": 4, + "src/plugins/loader.test.ts": 4, + "src/plugins/repo-locator.test.ts": 4, + "src/plugins/uninstall.test.ts": 4, + "src/provider/model-catalogs.test.ts": 4, + "src/session/anthropic-cache-prompt.test.ts": 4, + "src/subagent/agent-fleet-requires-tools.test.ts": 4, "src/subagent/mailbox-mail-drive.test.ts": 4, - "src/subagent/worktree.test.ts": 4, - "src/telemetry/feedback.test.ts": 4, - "src/tui/agent-progress.test.ts": 4, - "src/tui/chrome-state.test.ts": 4, - "src/tui/runtime-notices.test.ts": 4, - "scripts/eval-public-swe-one.test.ts": 3, - "src/agent/agent-search.test.ts": 3, - "src/agent/context-estimate.test.ts": 3, - "src/agent/directors/registry.test.ts": 3, - "src/agent/directors/tool-sets.test.ts": 3, - "src/agent/doom-loop-note.test.ts": 3, - "src/agent/lazy-blob-reader.test.ts": 3, - "src/agent/tasks.test.ts": 3, - "src/auth/oauth-scope-check.test.ts": 3, - "src/config/oauth-catalog.test.ts": 3, + "src/subagent/run-submit-result-rotation.test.ts": 4, + "src/tui/chrome-state-turn.test.ts": 4, + "src/tui/decision-truncation.test.ts": 4, + "src/tui/lockup.test.ts": 4, + "src/tui/markdown-parser.test.ts": 4, + "src/tui/overlay-list.test.ts": 4, + "src/tui/overlay-view.test.ts": 4, + "src/tui/plugin-diagnostics-sink.test.ts": 4, + "src/tui/row-retext.test.ts": 4, + "src/tui/run-snapshot-kind.test.ts": 4, + "scripts/check-gate.test.ts": 3, + "src/auth/codex/auth.test.ts": 3, + "src/auth/codex/session.test.ts": 3, "src/config/session-mode.test.ts": 3, - "src/cost/cost-visibility.test.ts": 3, - "src/inference-error-message.test.ts": 3, - "src/perf/assert-spans.test.ts": 3, - "src/perf/otel-config.test.ts": 3, - "src/perf/reactor-spans.test.ts": 3, - "src/permission/denial-memory.test.ts": 3, - "src/plugins/diagnostics.test.ts": 3, - "src/plugins/tool-plugins.test.ts": 3, - "src/plugins/tool-result-materialize.test.ts": 3, - "src/plugins/tool-result-secret-scrub.test.ts": 3, - "src/provider/context-window.test.ts": 3, - "src/provider/ollama.test.ts": 3, - "src/provider/reasoning-effort.test.ts": 3, - "src/renderer.test.ts": 3, - "src/session/attachment-store.test.ts": 3, - "src/session/run-sink.test.ts": 3, - "src/session/summarizer-excerpt.test.ts": 3, - "src/subagent/ask-director.test.ts": 3, - "src/subagent/refresh-inference-source.test.ts": 3, - "src/subagent/thrash.test.ts": 3, - "src/telemetry/ai-observability.test.ts": 3, - "src/tools/eval-http-env.test.ts": 3, - "src/tools/html-convert.test.ts": 3, - "src/tui/harness.test.ts": 3, - "src/tui/history-hydrate.test.ts": 3, - "src/tui/model-catalog.test.ts": 3, - "src/tui/prompt-border.test.ts": 3, - "src/tui/selection-copy.test.ts": 3, - "src/tui/session-queue.test.ts": 3, - "src/tui/stream-event-map.test.ts": 3, - "src/workflows/coordinator.test.ts": 3, - "src/agent/chat-event-subscribers.test.ts": 2, - "src/agent/codex-apply-patch.test.ts": 2, + "src/perf/index.test.ts": 3, + "src/perf/rollup.test.ts": 3, + "src/permission/critique-grep-file-env.test.ts": 3, + "src/plugins/bounded-grep-fallback.test.ts": 3, + "src/plugins/edit-file-diagnostics-plugin.test.ts": 3, + "src/plugins/marketplace.test.ts": 3, + "src/provider/codex-content-type-repair.test.ts": 3, + "src/session/live-model-switch.test.ts": 3, + "src/subagent/fleet-dry-drive.test.ts": 3, + "src/subagent/submit-result.test.ts": 3, + "src/tools/web-search.test.ts": 3, + "src/tui/diff.test.ts": 3, + "src/tui/focus/focus-state.test.ts": 3, + "src/tui/log-sink.test.ts": 3, + "src/tui/mark-anim.test.ts": 3, + "src/tui/mcp-copy-failure.test.ts": 3, + "src/tui/runner/wiring.skip-permissions-warning.test.ts": 3, + "src/tui/teardown.test.ts": 3, + "evals/capability/behaviors.test.ts": 2, + "scripts/generate-homebrew-tap.test.ts": 2, "src/agent/directors/explorer/package.test.ts": 2, "src/agent/directors/gauntlet/package.test.ts": 2, - "src/agent/directors/identity.test.ts": 2, - "src/agent/model-family-policy.test.ts": 2, - "src/agent/product-mutation-tools.test.ts": 2, - "src/agent/reactor-events.test.ts": 2, - "src/agent/search-scorer-parity.test.ts": 2, + "src/agent/mcp-promote-on-execute.test.ts": 2, + "src/agent/profiles-snapshot.test.ts": 2, "src/agent/tool-aliases.test.ts": 2, "src/agent/tool-schema-normalize.test.ts": 2, - "src/auth/callback-page.test.ts": 2, - "src/auth/codex/usage-limit-error.test.ts": 2, - "src/cost/cost-summary.test.ts": 2, - "src/cost/faremeter.test.ts": 2, - "src/cost/session-cost.test.ts": 2, + "src/agent/use-skill.test.ts": 2, + "src/auth/codex/session-failure.test.ts": 2, + "src/auth/credential-surface-coverage.test.ts": 2, + "src/auth/remove-provider.test.ts": 2, + "src/auth/xai/session.test.ts": 2, + "src/config/inference-sources.test.ts": 2, + "src/config/live-provider-context-window.test.ts": 2, + "src/crash/report.test.ts": 2, + "src/director-workflows.test.ts": 2, + "src/extensions/skills.test.ts": 2, + "src/inference-gateway-error.test.ts": 2, + "src/list-dir.test.ts": 2, + "src/mcp/client-auth-policy.test.ts": 2, + "src/perf/assert-spans.test.ts": 2, + "src/perf/attribution-report.test.ts": 2, + "src/plugins/data-only-commands.test.ts": 2, "src/plugins/origin-marker.test.ts": 2, - "src/plugins/register.test.ts": 2, - "src/provider/billing-product.test.ts": 2, - "src/provider/cache-ttl.test.ts": 2, - "src/shell/transparent-command.test.ts": 2, - "src/subagent/admission.test.ts": 2, - "src/subagent/authority.test.ts": 2, - "src/subagent/fleet-report.ask-wake.test.ts": 2, - "src/subagent/provider-family.test.ts": 2, - "src/telemetry/first-run.test.ts": 2, - "src/tools/ssrf-guard.test.ts": 2, - "src/tui/commands/registry.test.ts": 2, + "src/plugins/path-escape-plugin.test.ts": 2, + "src/plugins/rg-run.test.ts": 2, + "src/plugins/secret-guard-credential-surface.test.ts": 2, + "src/plugins/skill-commands.test.ts": 2, + "src/plugins/tool-output-uri-plugin.test.ts": 2, + "src/plugins/tool-result-materialize.test.ts": 2, + "src/pricing-fetcher.test.ts": 2, + "src/profiles.test.ts": 2, + "src/provider/codex-responses-sse.test.ts": 2, + "src/session/commit-signer.test.ts": 2, + "src/session/compaction-lifecycle.test.ts": 2, + "src/session/compactor-pairing.test.ts": 2, + "src/session/summarizer.test.ts": 2, + "src/subagent/agent-fleet-effort.test.ts": 2, + "src/subagent/ask-director.test.ts": 2, + "src/subagent/capability-preflight.test.ts": 2, + "src/subagent/run-audit-store.test.ts": 2, + "src/telemetry/ai-observability.test.ts": 2, + "src/tui/chrome-state.test.ts": 2, "src/tui/dynamic-tool-runner.test.ts": 2, - "src/tui/focus/focus-state.test.ts": 2, - "src/tui/mcp-result-format.test.ts": 2, - "src/tui/prompt-kill-ring.test.ts": 2, - "src/tui/runner/credential-recovery.test.ts": 2, + "src/tui/runner/reconnect-recovery.test.ts": 2, + "src/tui/runtime-notices.test.ts": 2, "src/tui/session-start.test.ts": 2, - "src/tui/stall-watchdog.test.ts": 2, + "src/tui/stream-event-map.test.ts": 2, + "src/tui/stream.test.ts": 2, "src/tui/submit-handler.test.ts": 2, - "src/tui/system-clipboard.test.ts": 2, - "src/tui/turns-to-blocks.test.ts": 2, - "src/tui/url-links.test.ts": 2, - "src/tui/width-columns.test.ts": 2, - "src/upgrade/index.test.ts": 2, + "src/tui/tool-formatter.test.ts": 2, + "src/tui/turn-state.test.ts": 2, + "src/tui/view/lines.test.ts": 2, + "src/workflows/runtime-persistence.test.ts": 2, + "evals/compaction/metrics.test.ts": 1, + "scripts/eval-public-swe-one.test.ts": 1, + "src/agent/agent-search.test.ts": 1, + "src/agent/apply-patch-tool.test.ts": 1, + "src/agent/canonical-tool-name.test.ts": 1, + "src/agent/chat-event-subscribers.test.ts": 1, + "src/agent/codex-apply-patch.test.ts": 1, + "src/agent/context-extensions.test.ts": 1, + "src/agent/director-compaction.test.ts": 1, + "src/agent/directors/attached-skills.test.ts": 1, "src/agent/directors/bruckheimer/package.test.ts": 1, "src/agent/directors/builder/package.test.ts": 1, "src/agent/directors/counsel/package.test.ts": 1, @@ -449,94 +374,199 @@ "src/agent/directors/migrator/package.test.ts": 1, "src/agent/directors/neckbeard/package.test.ts": 1, "src/agent/directors/prober/package.test.ts": 1, + "src/agent/directors/qa-lead/package.test.ts": 1, "src/agent/directors/rand/package.test.ts": 1, + "src/agent/directors/registry.test.ts": 1, "src/agent/directors/shakespeare/package.test.ts": 1, - "src/agent/directors/qa-lead/package.test.ts": 1, + "src/agent/directors/tool-sets.test.ts": 1, "src/agent/directors/warden/package.test.ts": 1, - "src/agent/grok-residual.test.ts": 1, - "src/agent/tool-classification.test.ts": 1, - "src/agent/worker-contract.test.ts": 1, + "src/agent/lazy-blob-reader.test.ts": 1, + "src/agent/lsp-availability.test.ts": 1, + "src/agent/model-family-policy.test.ts": 1, + "src/agent/product-mutation-tools.test.ts": 1, + "src/agent/search-scorer-parity.test.ts": 1, + "src/agent/skill-search.test.ts": 1, + "src/agent/tasks.test.ts": 1, + "src/auth/callback-page.test.ts": 1, + "src/auth/codex/usage-limit-error.test.ts": 1, "src/auth/credential-surface.test.ts": 1, + "src/auth/oauth-scope-check.test.ts": 1, + "src/changelog/index.test.ts": 1, "src/config/codex-providers.test.ts": 1, + "src/config/custom-provider-options.test.ts": 1, + "src/config/oauth-catalog.test.ts": 1, "src/config/oauth-providers.test.ts": 1, - "src/config/resolve-inference-spec.test.ts": 1, - "src/config/xai-providers.test.ts": 1, + "src/config/oauth-stores-race.test.ts": 1, + "src/config/onboarded-persistence.test.ts": 1, + "src/config/providers.test.ts": 1, + "src/cost/cost-summary.test.ts": 1, + "src/cost/cost-visibility.test.ts": 1, + "src/cost/session-cost.test.ts": 1, "src/diagnostic-sanitize.test.ts": 1, "src/inference-abort.test.ts": 1, + "src/inference-error-message.test.ts": 1, "src/logging/sink.test.ts": 1, - "src/mcp/client-auth-retry.test.ts": 1, "src/mcp/client-envelope.test.ts": 1, - "src/mcp/is-http-server.test.ts": 1, + "src/mcp/result-projection.test.ts": 1, "src/mcp/tool-name.test.ts": 1, - "src/mcp/tool-permissions.test.ts": 1, + "src/perf/otel-config.test.ts": 1, + "src/perf/otel-sink.test.ts": 1, + "src/permission/command.test.ts": 1, + "src/permission/denial-memory.test.ts": 1, + "src/permission/saved-skip-warning.test.ts": 1, + "src/permission/worktree-roots.test.ts": 1, + "src/plugins/agent-plugins.test.ts": 1, + "src/plugins/diagnostics.test.ts": 1, + "src/plugins/edit-file-line-range.test.ts": 1, "src/plugins/evidence-archive-path-guard.test.ts": 1, + "src/plugins/file-inspection-diagnosis.test.ts": 1, + "src/plugins/rg-output.test.ts": 1, + "src/plugins/tool-plugins.test.ts": 1, + "src/plugins/tool-result-secret-scrub.test.ts": 1, "src/plugins/tool-time-budget.test.ts": 1, - "src/provider/anthropic-cache-breakpoint.test.ts": 1, - "src/provider/anthropic-session-adapter.test.ts": 1, - "src/provider/bifrost-adapter.test.ts": 1, - "src/provider/context-window-thresholds.test.ts": 1, - "src/provider/identity-divergence.test.ts": 1, + "src/prompts.test.ts": 1, + "src/provider/cache-ttl.test.ts": 1, + "src/provider/chat-completions-usage.test.ts": 1, + "src/provider/codex-responses.test.ts": 1, + "src/provider/context-window.test.ts": 1, + "src/provider/grok-responses.test.ts": 1, + "src/provider/ollama.test.ts": 1, + "src/provider/openai-compatible-adapter.test.ts": 1, "src/provider/openai-responses.test.ts": 1, - "src/provider/opencode-go-adapter.test.ts": 1, - "src/session/active-run.test.ts": 1, - "src/session/compaction-archive-refs.test.ts": 1, - "src/session/resume-hint.test.ts": 1, + "src/provider/reasoning-effort.test.ts": 1, + "src/provider/replay-sanitizer.test.ts": 1, + "src/renderer.test.ts": 1, + "src/session/attachment-store.test.ts": 1, "src/session/run-sink-exec-status.test.ts": 1, - "src/session/shell-output-feed.test.ts": 1, - "src/subagent/lifecycle.test.ts": 1, + "src/session/run-sink.test.ts": 1, + "src/session/stream-consumer.test.ts": 1, + "src/session/summarizer-excerpt.test.ts": 1, + "src/shell/persistent-shell-cwd.test.ts": 1, + "src/subagent/authority.test.ts": 1, + "src/subagent/fleet-report.ask-wake.test.ts": 1, + "src/subagent/fleet-report.test.ts": 1, "src/subagent/poll-exempt.test.ts": 1, + "src/subagent/provider-family.test.ts": 1, + "src/subagent/refresh-inference-source.test.ts": 1, + "src/subagent/run-continuation-refresh.test.ts": 1, "src/subagent/shell-evidence.test.ts": 1, - "src/subagent/tool-preview.test.ts": 1, - "src/tui/agent-source-sync.test.ts": 1, - "src/tui/chrome-state-turn.test.ts": 1, + "src/subagent/thrash.test.ts": 1, + "src/subagent/trace-tool.test.ts": 1, + "src/subagent/worktree.test.ts": 1, + "src/telemetry/feedback.test.ts": 1, + "src/telemetry/first-run.test.ts": 1, + "src/tools/html-convert.test.ts": 1, + "src/tools/ssrf-guard.test.ts": 1, + "src/tui/agent-progress.test.ts": 1, + "src/tui/command-catalog.test.ts": 1, + "src/tui/command-display.test.ts": 1, "src/tui/command-registry-setup.test.ts": 1, - "src/tui/components/at-mention/parse.test.ts": 1, "src/tui/components/prompt-action-bar-label.test.ts": 1, + "src/tui/copy-path.test.ts": 1, "src/tui/exit-command.test.ts": 1, - "src/tui/live-session-port.test.ts": 1, - "src/tui/lockup.test.ts": 1, + "src/tui/geometry.test.ts": 1, + "src/tui/harness.test.ts": 1, "src/tui/mcp-catalog.test.ts": 1, "src/tui/mcp-list.test.ts": 1, - "src/tui/mention-filter.test.ts": 1, - "src/tui/notice-line.test.ts": 1, + "src/tui/mcp-result-format.test.ts": 1, + "src/tui/model-catalog.test.ts": 1, + "src/tui/overlay-reshape-selection.test.ts": 1, "src/tui/pending-column.test.ts": 1, "src/tui/pick-session.test.ts": 1, - "src/tui/plugin-surface.test.ts": 1, + "src/tui/prompt-attachments.test.ts": 1, + "src/tui/prompt-border.test.ts": 1, "src/tui/prompt-recognition.test.ts": 1, "src/tui/prompt-rows.test.ts": 1, - "src/tui/quota-retry.test.ts": 1, - "src/tui/ramp.test.ts": 1, - "src/tui/resolve-registered-tool-name.test.ts": 1, - "src/tui/runner-exit-code.test.ts": 1, + "src/tui/provider/steps.test.ts": 1, + "src/tui/queued-delivery.test.ts": 1, + "src/tui/runner/credential-recovery.test.ts": 1, "src/tui/runner/handoff.test.ts": 1, "src/tui/runner/mcp-trust-prompt-parity.test.ts": 1, "src/tui/runner/session.overlay-abort.test.ts": 1, - "src/tui/semantic-theme.test.ts": 1, "src/tui/sent-message-history.test.ts": 1, "src/tui/session-operation-queue.test.ts": 1, - "src/tui/tool-formatter-web-brand.test.ts": 1, - "src/tui/tool-subject.test.ts": 1, + "src/tui/session-queue.test.ts": 1, + "src/tui/stall-watchdog.test.ts": 1, + "src/tui/system-clipboard.test.ts": 1, + "src/tui/theme-contrast.test.ts": 1, + "src/tui/turns-to-blocks.test.ts": 1, + "src/tui/url-links.test.ts": 1, "src/tui/view/height.test.ts": 1, - "src/tui/view/registry.test.ts": 1, - "src/tui/view/validate.test.ts": 1, - "src/util/control-char-strip.test.ts": 1, - "src/util/tool-output-uri.test.ts": 1, + "src/tui/width-columns.test.ts": 1, + "src/upgrade/index.test.ts": 1, + "src/web/plugin-provider.test.ts": 1, "src/web/secret-scrub.test.ts": 1, "src/workflows/capabilities.test.ts": 1, - "src/workflows/registry.test.ts": 1, "src/workflows/runtime.test.ts": 1, + "testkit/defined.test.ts": 1, + "src/agent/context-estimate.test.ts": 0, + "src/agent/directors/identity.test.ts": 0, + "src/agent/doom-loop-note.test.ts": 0, + "src/agent/grok-residual.test.ts": 0, "src/agent/live-tool-dispatch.test.ts": 0, + "src/agent/reactor-events.test.ts": 0, + "src/agent/tool-classification.test.ts": 0, + "src/agent/worker-contract.test.ts": 0, "src/auth/token-session-boundary.test.ts": 0, + "src/auth/xai/auth-headers.test.ts": 0, + "src/config/resolve-inference-spec.test.ts": 0, + "src/config/xai-providers.test.ts": 0, + "src/cost/faremeter.test.ts": 0, + "src/mcp/client-auth-retry.test.ts": 0, + "src/mcp/client-unwrap.test.ts": 0, + "src/mcp/is-http-server.test.ts": 0, "src/mcp/stdio-env.test.ts": 0, - "src/plugins/rg-output.test.ts": 0, + "src/mcp/tool-permissions.test.ts": 0, + "src/perf/reactor-spans.test.ts": 0, + "src/plugins/register.test.ts": 0, + "src/provider/anthropic-cache-breakpoint.test.ts": 0, + "src/provider/anthropic-session-adapter.test.ts": 0, + "src/provider/bifrost-adapter.test.ts": 0, + "src/provider/billing-product.test.ts": 0, + "src/provider/context-window-thresholds.test.ts": 0, + "src/provider/identity-divergence.test.ts": 0, + "src/session/active-run.test.ts": 0, + "src/session/compaction-archive-refs.test.ts": 0, + "src/session/resume-hint.test.ts": 0, + "src/session/shell-output-feed.test.ts": 0, + "src/shell/transparent-command.test.ts": 0, + "src/subagent/admission.test.ts": 0, "src/subagent/inference-auth-failure.test.ts": 0, + "src/subagent/lifecycle.test.ts": 0, + "src/subagent/run-source.test.ts": 0, "src/subagent/run-suspended-send.test.ts": 0, + "src/subagent/tool-preview.test.ts": 0, "src/telemetry/singleton.test.ts": 0, - "testkit/defined.test.ts": 0, + "src/tools/eval-http-env.test.ts": 0, + "src/tui/agent-source-sync.test.ts": 0, + "src/tui/commands/registry.test.ts": 0, + "src/tui/components/at-mention/parse.test.ts": 0, "src/tui/components/session-header.test.ts": 0, + "src/tui/deliver-agent-message.test.ts": 0, + "src/tui/live-session-port.test.ts": 0, + "src/tui/mention-filter.test.ts": 0, + "src/tui/notice-line.test.ts": 0, + "src/tui/plugin-surface.test.ts": 0, + "src/tui/prompt-kill-ring.test.ts": 0, "src/tui/provider-failure-attempt.test.ts": 0, + "src/tui/quota-retry.test.ts": 0, + "src/tui/ramp.test.ts": 0, + "src/tui/resolve-registered-tool-name.test.ts": 0, "src/tui/resume-seed.test.ts": 0, + "src/tui/runner-exit-code.test.ts": 0, "src/tui/runner/send-failure-message.test.ts": 0, - "src/web/plugin-provider.test.ts": 0 + "src/tui/runner/submit.test.ts": 0, + "src/tui/selection-copy.test.ts": 0, + "src/tui/semantic-theme.test.ts": 0, + "src/tui/theme-detect.test.ts": 0, + "src/tui/tool-formatter-web-brand.test.ts": 0, + "src/tui/tool-subject.test.ts": 0, + "src/tui/view/validate.test.ts": 0, + "src/tui/width-contract.test.ts": 0, + "src/tui/workspace-watch.test.ts": 0, + "src/util/control-char-strip.test.ts": 0, + "src/util/tool-output-uri.test.ts": 0, + "src/workflows/coordinator.test.ts": 0, + "src/workflows/registry.test.ts": 0 } } diff --git a/scripts/test-parallel.test.ts b/scripts/test-parallel.test.ts index c95f0155b..f072b9657 100644 --- a/scripts/test-parallel.test.ts +++ b/scripts/test-parallel.test.ts @@ -23,8 +23,10 @@ function processAlive(pid: number): boolean { // remaining tests drive children that never emit output, so their stall is // declared deterministically on the watchdog's first tick; they use a short // window so the suite does not pay the tick test's headroom in wall clock. +// The silent-child window stays a few bun -e spawn times wide so a retried +// child's first output cannot be mistaken for a stall under load. const TEST_STALL_MS = 500; -const SILENT_CHILD_STALL_MS = 250; +const SILENT_CHILD_STALL_MS = 200; describe("runWithWatchdog", () => { test("passes through a successful run without retrying", async () => { diff --git a/src/auth/codex/refresh-lock.test.ts b/src/auth/codex/refresh-lock.test.ts index 49da151ec..723324a84 100644 --- a/src/auth/codex/refresh-lock.test.ts +++ b/src/auth/codex/refresh-lock.test.ts @@ -164,7 +164,10 @@ describe("codex refresh lock", () => { import.meta.url, ).pathname; const lock = join(dir, "refresh.lock"); - const proc = Bun.spawn(["bun", "run", holderPath, lock, "1500"], { + // Hold 600ms: the parent's 300ms acquisition attempt below must run + // entirely while the holder still holds (2x headroom), and the parent + // then waits for the holder to release, so the hold is the test's floor. + const proc = Bun.spawn(["bun", "run", holderPath, lock, "600"], { stdout: "pipe", stderr: "pipe", }); diff --git a/src/auth/codex/session-refresh-race.test.ts b/src/auth/codex/session-refresh-race.test.ts index 6ebdbaab5..fcee8d73d 100644 --- a/src/auth/codex/session-refresh-race.test.ts +++ b/src/auth/codex/session-refresh-race.test.ts @@ -103,9 +103,14 @@ describe("codex shared-credential refresh race", () => { if (arrivals === 1) { // Rendezvous: hold the first grant until the second refresh arrives // (or a timeout), forcing the two refreshes to overlap the way two - // concurrent headless runs do. + // concurrent headless runs do. The store serializes refreshes, so in + // the passing implementation the second session blocks on the lock + // and never starts a second grant: the hold always times out, so the + // ceiling is pure wall clock. 200ms is far past the few-ms window + // between two Promise.all-launched refreshes in a broken (unserialized) + // implementation, and the serialized path is unaffected. const start = Date.now(); - while (arrivals < 2 && Date.now() - start < 1_000) + while (arrivals < 2 && Date.now() - start < 200) await new Promise((resolve) => setTimeout(resolve, 5)); } const presented = new URLSearchParams(String(init?.body ?? "")).get( diff --git a/src/plugins/read-file-guard-plugin.test.ts b/src/plugins/read-file-guard-plugin.test.ts index 2410bfd43..555401f21 100644 --- a/src/plugins/read-file-guard-plugin.test.ts +++ b/src/plugins/read-file-guard-plugin.test.ts @@ -330,7 +330,13 @@ describe("readFileBounded", () => { }); describe("CL-8979 large-file pagination", () => { - const BIG_LINES = 45_000; + // The paging contract is ceiling-relative, so the chain tests inject a + // small ceiling (same as the single-line windowing test) and keep the + // fixture above it instead of 45k rows over the 8MB default. Paging is + // line-offset based: every hop re-reads the file from the start, so + // fixture size drives quadratic I/O (225 hops x up to 11MB before). + const BIG_SCAN_CEILING = 256 * 1024; + const BIG_LINES = 4_000; const bigRow = (i: number): string => `L${i}-` + "p".repeat(243); async function bigFixture(name: string): Promise { @@ -355,7 +361,9 @@ describe("CL-8979 large-file pagination", () => { id: string, args: Record, ) => Promise { - const plugin = readFileGuardPlugin(dir, {}); + const plugin = readFileGuardPlugin(dir, { + maxScanBytes: BIG_SCAN_CEILING, + }); const middleware = plugin.middleware; if (middleware === undefined) throw new Error("expected middleware"); const fallback = async (call: ToolCall): Promise => ({ @@ -374,6 +382,7 @@ describe("CL-8979 large-file pagination", () => { ): (id: string, args: Record) => Promise { const plugin = readFileGuardPlugin(dir, { blobReader: createBlobReader({ readBlob }), + maxScanBytes: BIG_SCAN_CEILING, }); const middleware = plugin.middleware; if (middleware === undefined) throw new Error("expected middleware"); @@ -390,9 +399,11 @@ describe("CL-8979 large-file pagination", () => { test("reads a deep page of a file larger than the scan ceiling", async () => { const p = await bigFixture("cl8979-big.txt"); - const res = await readFileBounded(p, 43_000, 5, neverAbort()); + const res = await readFileBounded(p, 3_500, 5, neverAbort(), { + maxScanBytes: BIG_SCAN_CEILING, + }); expect(res.isError).toBeUndefined(); - expect(String(res.content)).toContain(bigRow(43_000)); + expect(String(res.content)).toContain(bigRow(3_500)); expect(String(res.content)).not.toContain("scan limit"); }); @@ -484,17 +495,24 @@ describe("CL-8979 large-file pagination", () => { }); test("windows a single file line past the scan ceiling with exact reassembly", async () => { + // The windowing contract is ceiling-relative, so the test injects a small + // ceiling and keeps the fixture at ceiling+4096 instead of 8MB+4096. + // Paging is line-offset based: each hop re-reads the file from the start, + // so an 8MB fixture costs quadratic I/O (168 hops x up to 8MB). + const maxScanBytes = 256 * 1024; const filler = "0123456789ABCDEF".repeat( - Math.ceil((READ_FILE_MAX_SCAN_BYTES + 4096) / 16), + Math.ceil((maxScanBytes + 4096) / 16), ); const payload = `HEAD-${filler}-TAIL`; - expect(payload.length).toBeGreaterThan(READ_FILE_MAX_SCAN_BYTES); + expect(payload.length).toBeGreaterThan(maxScanBytes); const p = await fixture("cl8979-scan-giant.txt", `${payload}\nEND\n`); const rows: string[] = []; let offset = 0; let hops = 0; for (;;) { - const res = await readFileBounded(p, offset, 2000, neverAbort()); + const res = await readFileBounded(p, offset, 2000, neverAbort(), { + maxScanBytes, + }); hops += 1; expect(res.isError).toBeUndefined(); const content = String(res.content); diff --git a/src/plugins/read-file-guard-plugin.ts b/src/plugins/read-file-guard-plugin.ts index 737a86e29..9d4d83abe 100644 --- a/src/plugins/read-file-guard-plugin.ts +++ b/src/plugins/read-file-guard-plugin.ts @@ -62,6 +62,12 @@ export interface ReadFileGuardPluginOptions { canExecuteHostCommands?: () => boolean; /** Model-facing path for diagnosis text (the call argument, not the absolute). */ displayPath?: string; + /** + * Test-injectable scan ceiling in bytes. Production leaves this unset and + * keeps READ_FILE_MAX_SCAN_BYTES, so the ceiling is a constant in shipped + * code; tests lower it to keep oversized fixtures small. + */ + maxScanBytes?: number; } // A truncated read tells the model to continue with the same path and the @@ -140,6 +146,7 @@ function readStreamBounded( wrapLongLines?: boolean; windowHugeLines?: boolean; diagnoseFirstChunk?: (chunk: Buffer) => string | undefined; + maxScanBytes?: number; } = {}, ): Promise { return new Promise((resolveP, rejectP) => { @@ -148,7 +155,9 @@ function readStreamBounded( wrapLongLines = false, windowHugeLines = false, diagnoseFirstChunk, + maxScanBytes, } = options; + const scanCeiling = maxScanBytes ?? READ_FILE_MAX_SCAN_BYTES; const decoder = new StringDecoder("utf8"); const contentBudget = READ_FILE_MAX_BYTES - NOTICE_RESERVE_BYTES; @@ -290,7 +299,7 @@ function readStreamBounded( } else { done({ content: `[reached the ${ - READ_FILE_MAX_SCAN_BYTES / (1024 * 1024) + scanCeiling / (1024 * 1024) }MB scan limit before offset ${offset}; the file is larger than read_file scans in one pass. Use a smaller offset or grep to locate content.]`, isError: true, }); @@ -300,7 +309,7 @@ function readStreamBounded( let content = out.join("\n"); if (truncReason !== undefined) { - content += `\n\n${continuationNotice(truncReason, offset + 1, lastEmittedLine, limit)}`; + content += `\n\n${continuationNotice(truncReason, offset + 1, lastEmittedLine, limit, scanCeiling)}`; } done({ content }); }; @@ -331,7 +340,7 @@ function readStreamBounded( finishOk(); return; } - if (scanned >= READ_FILE_MAX_SCAN_BYTES) { + if (scanned >= scanCeiling) { flushRemainder(); if (truncReason === undefined) truncReason = "scan"; finishOk(); @@ -368,7 +377,7 @@ export function readFileBounded( signal: AbortSignal, inspection?: Pick< ReadFileGuardPluginOptions, - "whichExtractor" | "canExecuteHostCommands" | "displayPath" + "whichExtractor" | "canExecuteHostCommands" | "displayPath" | "maxScanBytes" >, ): Promise { const labeledPath = inspection?.displayPath ?? absolutePath; @@ -381,6 +390,9 @@ export function readFileBounded( { mapStreamError: (err) => mapFilesystemStreamError(labeledPath, err), windowHugeLines: true, + ...(inspection?.maxScanBytes !== undefined + ? { maxScanBytes: inspection.maxScanBytes } + : {}), diagnoseFirstChunk: (chunk) => { const kind = inspectionKindFromFirstChunk(absolutePath, chunk); if (kind === undefined) return undefined; @@ -434,6 +446,7 @@ function continuationNotice( firstLine: number, lastLine: number, limit: number, + scanCeiling: number, ): string { const next = `Use offset=${lastLine} to continue.`; if (reason === "lines") { @@ -445,7 +458,7 @@ function continuationNotice( }KB output limit. ${next}]`; } return `[Showing lines ${firstLine}-${lastLine}; stopped at the ${ - READ_FILE_MAX_SCAN_BYTES / (1024 * 1024) + scanCeiling / (1024 * 1024) }MB scan limit. ${next}]`; } @@ -475,10 +488,12 @@ export function readFileGuardPlugin( cwd: string, options: ReadFileGuardPluginOptions = {}, ): ToolPlugin { - const { blobReader, whichExtractor, canExecuteHostCommands } = options; + const { blobReader, whichExtractor, canExecuteHostCommands, maxScanBytes } = + options; const inspection = { ...(whichExtractor !== undefined ? { whichExtractor } : {}), ...(canExecuteHostCommands !== undefined ? { canExecuteHostCommands } : {}), + ...(maxScanBytes !== undefined ? { maxScanBytes } : {}), }; return { middleware: (next) => async (call, signal) => { diff --git a/src/subagent/run-shell-child-reap.test.ts b/src/subagent/run-shell-child-reap.test.ts index b89beb15c..f0a08fd5b 100644 --- a/src/subagent/run-shell-child-reap.test.ts +++ b/src/subagent/run-shell-child-reap.test.ts @@ -287,8 +287,14 @@ describe("CL-7990 shell-child reap: sessions holding a live shell child settle", const outcome = await runWithStubAgent(agent, async () => { const { runPromise, handles } = await runWithShellChildAgent(agent, { persist: false, + // Test injection: the run's own bounded teardown fires after this + // short deadline instead of the production 30s bound, so the + // wedged close is proven to settle the run in ~0.4s, not 30s. + // Kept above the close bound below so the close error surfaces + // before the run settles. + teardownDeadlineMs: 400, }); - const closeError = await handles.close(500).then( + const closeError = await handles.close(200).then( () => undefined, (err: unknown) => err, ); diff --git a/src/subagent/run.ts b/src/subagent/run.ts index 234c13f7c..bec36a337 100644 --- a/src/subagent/run.ts +++ b/src/subagent/run.ts @@ -1986,7 +1986,9 @@ async function runSubAgentInner( } if (!persisting) { // Bounded like close_agent: a session teardown wedged by a live shell - // descendant fails the run instead of parking it forever. + // descendant fails the run instead of parking it forever. Tests inject + // a short deadline so wedged-close suites do not pay the production + // 30s bound; production never sets it and keeps the default. await awaitBoundedTeardown( disposeSubAgentSession({ signal: runController.signal, @@ -1995,7 +1997,7 @@ async function runSubAgentInner( ...(streamPromise !== undefined ? { streamPromise } : {}), posixTools, }), - DEFAULT_CLOSE_DEADLINE_MS, + params.teardownDeadlineMs ?? DEFAULT_CLOSE_DEADLINE_MS, ); } } diff --git a/src/subagent/types.ts b/src/subagent/types.ts index ed5a585bc..a0ad433b7 100644 --- a/src/subagent/types.ts +++ b/src/subagent/types.ts @@ -234,6 +234,13 @@ export type RunSubAgentParams = { * leaks its posix tools / workdir lock. */ persist?: boolean; + /** + * Override the bounded teardown deadline (ms) for the run's own + * end-of-turn teardown on the non-persist path. Tests inject a short + * deadline so wedged-close suites do not pay the production 30s bound; + * production never sets it and keeps the default. + */ + teardownDeadlineMs?: number; /** * Narrow session-store port for leaf `ask_director`. The store owns the * pending Promise; this run only registers and awaits. Omit when the