From a3a8271f850d4cea7057b26bd1e2560ccc647003 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 13 Sep 2026 23:19:46 -0700 Subject: [PATCH 1/3] ci(test): shard suite into four time-balanced slices --- .github/workflows/ci.yml | 91 +++++-- CONTRIBUTING.md | 11 +- scripts/ci-timings.json | 516 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 596 insertions(+), 22 deletions(-) create mode 100644 scripts/ci-timings.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b38006e1..f70cc0818 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,22 +78,29 @@ jobs: run: bun run build # The suite is sharded so the slowest slice, not the whole suite, sets the - # wall clock. Every shard still goes through check:projects-dir-guard: the - # guard forwards these path filters to the suite it wraps, and the union of - # the shards' filters is exactly ./src ./tests ./evals ./scripts, so the gate covers - # the same tests as before, all of them sandboxed. + # wall clock. Four time-balanced shards split the same + # ./src ./tests ./evals ./scripts union via bun's --shard=k/4, balanced by + # the checked-in per-file durations in scripts/ci-timings.json (--timings). + # Every shard still goes through check:projects-dir-guard: the guard + # forwards the path union plus the shard flags to the suite it wraps, so the + # gate covers the same tests as before, all of them sandboxed. + # + # Timings refresh policy: regenerate scripts/ci-timings.json by running the + # full union locally with --update-timings (same seeded flags as `test`): + # bun run check:projects-dir-guard ./src ./tests ./evals ./scripts \ + # --timings=./scripts/ci-timings.json --update-timings + # Regen when the slowest shard's Test step skews more than ~20% above a + # quarter of the one-process suite time (shards drifting apart means the + # timings no longer describe the suite), or proactively whenever slow files + # land. A scheduled refresh artifact is a future option, not current setup. test: runs-on: ubuntu-latest strategy: - # A red shard must not cancel the other; both results are the signal. + # A red shard must not cancel the others; all results are the signal. fail-fast: false matrix: - shard: - - name: src - paths: ./src - - name: tests-evals-and-scripts - paths: ./tests ./evals ./scripts - name: test (${{ matrix.shard.name }}) + shard: ["1/4", "2/4", "3/4", "4/4"] + name: test (${{ matrix.shard }}) steps: - name: Checkout uses: actions/checkout@v4 @@ -122,19 +129,63 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile - # The same script the local `bun run check` gate runs, with the shard's - # path filters forwarded through the guard to the suite. The guard - # routes a filtered run through test:paths, which carries the same - # seeded flags as the `test` script; bun test filters are additive, so - # appending filters to `bun run test` could not narrow it. Randomized - # order catches tests that only pass in the default file order (shared - # module-level state, an unrestored global mock, a leaked env var). + # The same script the local `bun run check` gate runs, with the full + # path union plus the shard's --shard/--timings flags forwarded through + # the guard to the suite. The guard routes a filtered run through + # test:paths, which carries the same seeded flags as the `test` script; + # bun test filters are additive, so appending filters to `bun run test` + # could not narrow it. --shard splits by file (balanced by --timings), + # so every shard covers the same union and the four shards together + # cover the whole suite. Randomized order catches tests that only pass + # in the default file order (shared module-level state, an unrestored + # global mock, a leaked env var). # The seed stays 424242 in every shard rather than varying per shard: # the shards already run disjoint file sets, and a fixed seed keeps # any failure reproducible locally with the same - # `bun run test:paths `. + # `bun run test:paths --shard=k/4 + # --timings=./scripts/ci-timings.json`. - name: Test - run: bun run check:projects-dir-guard ${{ matrix.shard.paths }} + run: bun run check:projects-dir-guard ./src ./tests ./evals ./scripts --shard=${{ matrix.shard }} --timings=./scripts/ci-timings.json + + # Cross-shard pollution detector: the shards above split the path union, + # but the union is not the isolation domain — a mock.module leak across + # files fails in the one-process suite yet passes when the files land in + # different shards (CL-6967). This job reruns the whole union in one + # process with no filters, exactly like the local `bun run check` gate. + # Non-blocking (continue-on-error) so a slow or flaky full run cannot hold + # the gate; a real pollution failure still shows up red for triage. + test-one-process: + name: test (one-process pollution detector) + continue-on-error: true + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.14" + + - name: Install ripgrep + run: sudo apt-get install -y ripgrep + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: node_modules + key: bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Test + run: bun run check:projects-dir-guard # protect-main still requires the pre-restructure check names. These jobs # exist only to publish those contexts after the real work succeeds. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 448caeccf..52e004584 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,8 +30,15 @@ bun run build bun run test ``` -These match the local development loop. CI shards the same path union via -`test:paths` rather than running the one-process `bun run test` suite. +These match the local development loop. CI splits the same path union into +four time-balanced `--shard=k/4` slices via `test:paths` (balanced by the +checked-in per-file durations in `scripts/ci-timings.json`) rather than +running the one-process `bun run test` suite. Regenerate that file with +`bun run check:projects-dir-guard ./src ./tests ./evals ./scripts +--timings=./scripts/ci-timings.json --update-timings` when the slowest +shard skews more than ~20% above a quarter of the one-process suite time, +or proactively whenever slow files land — see `.github/workflows/ci.yml` +for the full policy. Run `bun run check` (lint, typecheck, build, and the guarded test suite) before opening a PR — `bun run test` alone skips the projects-dir sandbox guard, which only runs diff --git a/scripts/ci-timings.json b/scripts/ci-timings.json new file mode 100644 index 000000000..a9583212c --- /dev/null +++ b/scripts/ci-timings.json @@ -0,0 +1,516 @@ +{ + "version": 1, + "files": { + "scripts/test-parallel.test.ts": 12628, + "src/permission/permission.test.ts": 10178, + "tests/integration/exec-shutdown-reap.test.ts": 7630, + "src/plugins/shell-guard-plugin.test.ts": 7184, + "src/shell/background-shell.test.ts": 6911, + "tests/integration/crash-finalize.test.ts": 6404, + "tests/integration/compaction-baseline.test.ts": 4868, + "tests/integration/exec-signal-finalize.test.ts": 4296, + "src/tui/runtime-channels.test.ts": 4100, + "tests/integration/signal-finalize.test.ts": 4040, + "src/subagent/spawn-agent-worktree.test.ts": 3272, + "src/auth/store.test.ts": 3133, + "src/tools/web-fetch.test.ts": 3128, + "tests/integration/rawmode-sigint.test.ts": 3127, + "src/subagent/run-resolved-provider-failure.test.ts": 2958, + "src/tui/transcript-layout.test.ts": 2834, + "src/tui/tool-execution-watchdog.test.ts": 2583, + "src/tui/product-host.test.ts": 2504, + "src/session/list-sessions.test.ts": 2358, + "tests/integration/git-push-scoped.test.ts": 2323, + "src/tui/transcript-panels.test.ts": 2158, + "tests/integration/subagent-permission.test.ts": 2024, + "src/tui/mcp-copy-failure.test.ts": 1702, + "tests/unit/verify-corbits-only-scope.test.ts": 1625, + "src/permission/gate.test.ts": 1581, + "src/subagent/agent-fleet.test.ts": 1336, + "src/tui/collapse.test.ts": 1314, + "src/tui/provider-setup.test.ts": 1287, + "src/tui/markdown-rows.test.ts": 1274, + "src/tui/command-surfaces.test.ts": 1265, + "src/agent/environment.test.ts": 1205, + "src/tui/slash-popup-gate.test.ts": 1181, + "src/agent/background-shell-tool.test.ts": 1159, + "src/tui/transcript-long-log-scroll.test.ts": 1116, + "src/tui/url-click.test.ts": 1115, + "src/session/project-key.test.ts": 1114, + "src/tui/image-attachments.test.ts": 1082, + "src/agent/tool-search.test.ts": 1010, + "tests/unit/oxlint-no-bare-mock-module.test.ts": 1004, + "tests/unit/prepare-homebrew-tap-release.test.ts": 987, + "tests/unit/workflow-host.test.ts": 982, + "src/tui/runtime-bridge.test.ts": 804, + "src/config.test.ts": 800, + "tests/integration/reactor-approval-suspend.test.ts": 788, + "src/session/run-liveness.test.ts": 727, + "src/session/optimized-context-store.test.ts": 655, + "src/session/session-dir.test.ts": 608, + "tests/unit/telemetry.test.ts": 605, + "src/tui/shell.test.ts": 577, + "src/tui/gate-wire.test.ts": 572, + "src/session/sent-messages.test.ts": 533, + "src/state.test.ts": 511, + "tests/unit/tui/at-mention-resolution.test.ts": 503, + "tests/integration/reactor-permission-multi-turn.test.ts": 501, + "tests/helpers/temporary-git-repo.test.ts": 494, + "src/tui/transcript-anchor.test.ts": 467, + "scripts/eval-capability.test.ts": 425, + "src/mcp/auth-store.test.ts": 422, + "src/permission/worktree-roots.test.ts": 419, + "src/tui/wave6.test.ts": 383, + "src/tui/request-approval.test.ts": 357, + "src/permission/classify-security.test.ts": 349, + "tests/unit/exec/runner.test.ts": 345, + "src/agent/fleet-verbs-mount.test.ts": 340, + "src/tui/landing.test.ts": 326, + "src/plugins/permission-plugin.test.ts": 323, + "tests/unit/session/run-state-e2e.test.ts": 320, + "src/tui/keybindings.test.ts": 314, + "src/agent/posix-tool-plugins.test.ts": 311, + "src/session/rename-session.test.ts": 310, + "src/plugins/secret-guard-symlink.test.ts": 304, + "src/agent/exa-web-fetch-alias.test.ts": 302, + "tests/unit/telemetry-toggle.test.ts": 282, + "src/permission/project-approvals-trust.test.ts": 261, + "tests/integration/mcp-late-dispatch.test.ts": 260, + "src/permission/reactor-authorize.test.ts": 256, + "src/subagent/run-skill-scope.test.ts": 245, + "src/subagent/run-authority.test.ts": 242, + "src/session/hooks.test.ts": 230, + "tests/unit/workflows-state.test.ts": 223, + "tests/unit/ripgrep-plugin.test.ts": 219, + "src/tui/runner-host.test.ts": 217, + "src/perf/permission-subagent-spans.test.ts": 215, + "src/tui/turn-monitor.test.ts": 205, + "src/subagent/run-persist-close.test.ts": 200, + "src/tui/mention-popup.test.ts": 199, + "src/tui/prompt-slash-exit.test.ts": 190, + "src/subagent/followup-live-agent.test.ts": 182, + "src/permission/grant-scope.test.ts": 179, + "src/permission/queue.test.ts": 173, + "src/tui/overlays.test.ts": 173, + "src/session/runtime-assembly.test.ts": 164, + "tests/unit/hooks.test.ts": 151, + "src/session/state.test.ts": 142, + "src/agent/apply-patch-diff.test.ts": 140, + "src/subagent/index.test.ts": 140, + "src/tui/prompt-features.test.ts": 137, + "tests/integration/reactor-events-guards.test.ts": 137, + "src/tui/palette-paint.test.ts": 135, + "src/agent/tools-mcp-disconnect.test.ts": 134, + "src/tui/agent-ask-wake.test.ts": 123, + "src/tui/runner/wiring.ask-wake.test.ts": 123, + "src/tui/observe-live.test.ts": 122, + "tests/unit/telemetry-product-events.test.ts": 121, + "src/director.test.ts": 116, + "src/subagent/lifecycle-tools.test.ts": 115, + "src/tui/focus-routing.test.ts": 114, + "src/permission/path-restriction.test.ts": 111, + "src/plugins/secret-guard-shell-symlink.test.ts": 111, + "tests/unit/session/resume-interrupted.test.ts": 110, + "src/permission/approval-log.test.ts": 109, + "src/tui/row-click.test.ts": 108, + "src/tui/runner/exit.test.ts": 106, + "src/tui/overlay-paint.test.ts": 105, + "src/session/session-label.test.ts": 104, + "tests/unit/mcp.test.ts": 100, + "tests/unit/project-trust-plugins.test.ts": 100, + "src/tui/gutter-labels.test.ts": 98, + "src/subagent/session-store.test.ts": 96, + "src/plugins/delete-file-plugin.test.ts": 95, + "src/tui/prompt-chrome.test.ts": 94, + "src/session/stream-journal.test.ts": 93, + "tests/unit/data-only-commands.test.ts": 91, + "src/mcp/oauth-provider.test.ts": 90, + "src/tui/components/at-mention/list.test.ts": 90, + "src/tui/list-modal.test.ts": 90, + "src/permission/workspace-containment.test.ts": 88, + "src/plugins/read-file-guard-plugin.test.ts": 86, + "src/settings.test.ts": 86, + "src/tui/prompt-box.test.ts": 84, + "src/tui/diff-rows.test.ts": 83, + "tests/unit/path-trust.test.ts": 82, + "src/tui/overlay-overflow.test.ts": 81, + "src/session/compaction-archive.test.ts": 79, + "src/subagent/run-settlement.test.ts": 76, + "tests/unit/config.test.ts": 75, + "tests/integration/compaction-atomicity.test.ts": 74, + "tests/unit/data-only-agent.test.ts": 74, + "src/mcp/add-server.test.ts": 71, + "src/tui/wave7.test.ts": 71, + "tests/unit/codex-session.test.ts": 71, + "tests/unit/index.test.ts": 71, + "src/tui/approval-prompt-visibility.test.ts": 69, + "tests/integration/vendored-carry.test.ts": 69, + "src/mcp/plugin.test.ts": 65, + "src/trust/project-trust.test.ts": 65, + "src/mcp/client-auth-reauth-cap.test.ts": 63, + "src/tui/run-snapshot-kind.test.ts": 61, + "src/tui/steer-worker-invariant.test.ts": 61, + "src/mcp/callback-server.test.ts": 60, + "src/tui/queued-delivery-hop.test.ts": 59, + "src/tui/row-update-perf.test.ts": 58, + "src/plugins/secret-guard-plugin.test.ts": 56, + "tests/unit/tui/agent-tools.test.ts": 56, + "src/plugins/verify-plugin.test.ts": 54, + "src/subagent/retain-salvage.test.ts": 54, + "src/tui/onboarding.test.ts": 54, + "src/tui/copy-wire.test.ts": 53, + "src/subagent/trace-reader.test.ts": 51, + "tests/unit/xai-session.test.ts": 51, + "src/tui/provider-setup-submit.test.ts": 50, + "src/tui/tool-rows.test.ts": 50, + "tests/integration/reactor-empty-turn.test.ts": 50, + "src/subagent/intervention-log.test.ts": 48, + "src/plugins/change-diff.test.ts": 47, + "src/permission/critique-grep-file-env.test.ts": 45, + "tests/unit/codex-callback-server.test.ts": 45, + "tests/unit/project-trust.test.ts": 45, + "src/session/incremental-jsonl.test.ts": 44, + "src/tui/welcome.test.ts": 44, + "src/subagent/run-submit-result-rotation.test.ts": 41, + "tests/unit/path-plugin-trust.test.ts": 41, + "src/session/live-model-switch.test.ts": 40, + "src/tools/web-search.test.ts": 40, + "src/profiles.test.ts": 39, + "tests/unit/permission/cross-commit-composition.test.ts": 37, + "src/plugins/bounded-grep-fallback.test.ts": 36, + "src/agent/product-mutation-tools.test.ts": 35, + "src/permission/store.test.ts": 35, + "src/subagent/run-audit-store.test.ts": 35, + "src/tui/margins.test.ts": 35, + "src/tui/reasoning-fold.test.ts": 32, + "tests/unit/approval-resume.test.ts": 32, + "tests/unit/skills.test.ts": 32, + "tests/unit/vendor-patch-ledger.test.ts": 32, + "src/tui/ramp-paint.test.ts": 31, + "tests/unit/plugin-repo-locator.test.ts": 30, + "tests/unit/skill-commands.test.ts": 30, + "src/tui/overlay-empty-state.test.ts": 29, + "src/provider/validate-connection.test.ts": 28, + "tests/unit/workflows-runtime-persistence.test.ts": 28, + "src/crash/report.test.ts": 27, + "src/tui/startup-transcript.test.ts": 27, + "tests/unit/plugin-marketplace.test.ts": 27, + "src/tui/prompt-highlight.test.ts": 26, + "src/tui/description-zone.test.ts": 25, + "src/tui/overlay-primary-state.test.ts": 25, + "tests/unit/generate-homebrew-tap.test.ts": 25, + "src/pricing-metadata.test.ts": 24, + "src/tui/runtime-shutdown.test.ts": 24, + "src/shell/persistent-shell-cwd.test.ts": 23, + "src/plugins/claude-plugins.test.ts": 22, + "src/tui/thinking-reveal.test.ts": 21, + "src/agent/use-skill.test.ts": 20, + "src/plugins/uninstall.test.ts": 20, + "src/tui/overlay-key-routing.test.ts": 20, + "tests/unit/codex-auth.test.ts": 20, + "src/plugins/evidence-archive-search-plugin.test.ts": 19, + "tests/unit/plugin-loader-path.test.ts": 19, + "src/plugins/authz-plugin.test.ts": 17, + "src/plugins/secret-guard-credential-surface.test.ts": 17, + "src/auth/xai/callback-server.test.ts": 16, + "src/list-dir.test.ts": 16, + "src/plugins/edit-file-diagnostics-plugin.test.ts": 16, + "src/tui/markdown-parser.test.ts": 16, + "src/tui/mcp-view.test.ts": 16, + "src/tui/overlay-body.test.ts": 16, + "src/tui/overlay-min-geometry.test.ts": 16, + "src/tui/runner/wiring.stall-poll.test.ts": 16, + "tests/unit/corbits-skills-catalog.test.ts": 16, + "src/tui/chrome-repaint.test.ts": 15, + "src/tui/render-loop.test.ts": 15, + "src/tui/plugin-diagnostics-sink.test.ts": 14, + "src/tui/overlay-float-reset.test.ts": 13, + "tests/unit/inference-response-kind.test.ts": 13, + "src/agent/prompt-sizes.test.ts": 12, + "src/plugins/loader.test.ts": 12, + "src/tui/resume-seed.test.ts": 12, + "src/tui/runtime-bridge-coalesce.test.ts": 12, + "src/util/control-char-strip.test.ts": 12, + "evals/capability/lib.test.ts": 11, + "src/perf/rollup.test.ts": 11, + "src/tui/overlay-fixture-fallback.test.ts": 11, + "src/tui/provider-connect.test.ts": 11, + "src/tui/stream-event-map.test.ts": 11, + "src/plugins/diagnostics.test.ts": 10, + "src/subagent/nudge-director.test.ts": 10, + "src/auth/credential-surface-coverage.test.ts": 9, + "src/context-compactor.test.ts": 9, + "src/perf/index.test.ts": 9, + "src/permission/auto-shell-policy.test.ts": 9, + "src/plugins/edit-file-line-range.test.ts": 9, + "src/shell/run-shell-authz.test.ts": 9, + "src/subagent/trace-tool.test.ts": 9, + "src/tui/mouse-reporting-disabled.test.ts": 9, + "src/tui/overlay-body-cache-staleness.test.ts": 9, + "src/plugins/path-escape-plugin.test.ts": 8, + "src/renderer.test.ts": 8, + "src/subagent/fleet-report.test.ts": 8, + "src/subagent/run-source.test.ts": 8, + "src/tools/html-convert.test.ts": 8, + "src/tui/overlay-reshape-selection.test.ts": 8, + "tests/unit/lexicon-skill.test.ts": 8, + "src/plugins/result-truncation-plugin.test.ts": 7, + "src/prompts.test.ts": 7, + "src/subagent/fleet-dry-drive.test.ts": 7, + "src/subagent/submit-result.test.ts": 7, + "src/telemetry/ai-observability.test.ts": 7, + "src/tui/components/session-header.test.ts": 7, + "src/tui/decision-truncation.test.ts": 7, + "src/tui/log-sink.test.ts": 7, + "src/tui/teardown.test.ts": 7, + "tests/unit/workflows-runtime.test.ts": 7, + "src/agent/director.test.ts": 6, + "src/agent/directors/intern/package.test.ts": 6, + "src/perf/attribution-report.test.ts": 6, + "src/session/commit-signer.test.ts": 6, + "src/tui/row-retext.test.ts": 6, + "tests/unit/compactor-pairing.test.ts": 6, + "src/agent/codex-tool-mount.test.ts": 5, + "src/agent/prompts.test.ts": 5, + "src/pricing-fetcher.test.ts": 5, + "src/subagent/thrash.test.ts": 5, + "src/tui/mark-anim.test.ts": 5, + "src/tui/overlay-list.test.ts": 5, + "src/tui/overlay-view.test.ts": 5, + "src/tui/tool-formatter.test.ts": 5, + "src/tui/turn-state.test.ts": 5, + "src/tui/width-columns.test.ts": 5, + "tests/unit/inference-sources.test.ts": 5, + "tests/unit/summarizer.test.ts": 5, + "tests/unit/workflows-director.test.ts": 5, + "evals/capability/locked-fixtures.test.ts": 4, + "src/agent/codex-tool-proxies.test.ts": 4, + "src/agent/compaction.test.ts": 4, + "src/perf/assert-spans.test.ts": 4, + "src/plugins/tool-output-uri-plugin.test.ts": 4, + "src/provider/opencode-go-models.test.ts": 4, + "src/provider/replay-sanitizer.test.ts": 4, + "src/provider/zen-models.test.ts": 4, + "src/session/assemble-runtime.test.ts": 4, + "src/subagent/mailbox-mail-drive.test.ts": 4, + "src/subagent/worktree.test.ts": 4, + "src/tui/diff.test.ts": 4, + "src/tui/session-chrome.test.ts": 4, + "tests/unit/agent-context-extensions.test.ts": 4, + "tests/unit/codex-sse-fixtures.test.ts": 4, + "tests/unit/tui/onboarded-persistence.test.ts": 4, + "tests/unit/tui/runner.test.ts": 4, + "evals/capability/behaviors.test.ts": 3, + "src/agent/directors/emil/package.test.ts": 3, + "src/agent/directors/registry.test.ts": 3, + "src/agent/directors/skywalker/package.test.ts": 3, + "src/agent/lsp-availability.test.ts": 3, + "src/agent/skill-search.test.ts": 3, + "src/cost/cost-summary.test.ts": 3, + "src/mcp/client-auth-policy.test.ts": 3, + "src/perf/otel-sink.test.ts": 3, + "src/permission/command.test.ts": 3, + "src/plugins/data-only-agent.test.ts": 3, + "src/plugins/rg-run.test.ts": 3, + "src/provider/context-window.test.ts": 3, + "src/session/approval-resume.test.ts": 3, + "src/subagent/ask-director.test.ts": 3, + "src/tools/ssrf-guard.test.ts": 3, + "src/tui/chrome-state.test.ts": 3, + "src/tui/command-display.test.ts": 3, + "src/tui/components/prompt-action-bar-label.test.ts": 3, + "src/tui/correlation-acceptance.test.ts": 3, + "src/tui/harness.test.ts": 3, + "src/tui/lockup.test.ts": 3, + "src/tui/prompt-border.test.ts": 3, + "src/tui/queued-delivery.test.ts": 3, + "src/tui/stream.test.ts": 3, + "src/tui/syntax-highlight.test.ts": 3, + "tests/unit/agent/tasks.test.ts": 3, + "tests/unit/pricing-fetcher.test.ts": 3, + "tests/unit/tui/run-sink.test.ts": 3, + "tests/unit/workflows-definitions.test.ts": 3, + "scripts/eval-public-swe-one.test.ts": 2, + "src/agent/agent-search.test.ts": 2, + "src/agent/codex-apply-patch.test.ts": 2, + "src/agent/directors/bruckheimer/package.test.ts": 2, + "src/agent/directors/builder/package.test.ts": 2, + "src/agent/directors/greybeard/package.test.ts": 2, + "src/agent/directors/migrator/package.test.ts": 2, + "src/agent/lazy-blob-reader.test.ts": 2, + "src/agent/retry-policy.test.ts": 2, + "src/agent/tool-schema-normalize.test.ts": 2, + "src/auth/callback-page.test.ts": 2, + "src/auth/oauth-scope-check.test.ts": 2, + "src/changelog/index.test.ts": 2, + "src/config/inference-sources.test.ts": 2, + "src/config/oauth-catalog.test.ts": 2, + "src/config/providers.test.ts": 2, + "src/inference-error-message.test.ts": 2, + "src/inference-gateway-error.test.ts": 2, + "src/logging/sink.test.ts": 2, + "src/perf/otel-config.test.ts": 2, + "src/perf/reactor-spans.test.ts": 2, + "src/plugins/agent-plugins.test.ts": 2, + "src/plugins/tool-plugins.test.ts": 2, + "src/plugins/tool-result-secret-scrub.test.ts": 2, + "src/provider/codex-responses.test.ts": 2, + "src/provider/grok-responses.test.ts": 2, + "src/provider/identity-divergence.test.ts": 2, + "src/provider/ollama.test.ts": 2, + "src/provider/opencode-go-adapter.test.ts": 2, + "src/provider/reasoning-effort.test.ts": 2, + "src/session/run-sink.test.ts": 2, + "src/session/shell-output-feed.test.ts": 2, + "src/session/summary-excerpt.test.ts": 2, + "src/subagent/inference-auth-failure.test.ts": 2, + "src/subagent/poll-exempt.test.ts": 2, + "src/subagent/shell-evidence.test.ts": 2, + "src/telemetry/feedback.test.ts": 2, + "src/tui/agent-progress.test.ts": 2, + "src/tui/command-registry-setup.test.ts": 2, + "src/tui/commands/registry.test.ts": 2, + "src/tui/components/at-mention/parse.test.ts": 2, + "src/tui/copy-path.test.ts": 2, + "src/tui/focus/focus-state.test.ts": 2, + "src/tui/geometry.test.ts": 2, + "src/tui/history-hydrate.test.ts": 2, + "src/tui/model-catalog.test.ts": 2, + "src/tui/pick-session.test.ts": 2, + "src/tui/plugin-surface.test.ts": 2, + "src/tui/prompt-attachments.test.ts": 2, + "src/tui/prompt-recognition.test.ts": 2, + "src/tui/provider-failure-attempt.test.ts": 2, + "src/tui/runtime-notices.test.ts": 2, + "src/tui/selection-copy.test.ts": 2, + "src/tui/session-start.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/upgrade/index.test.ts": 2, + "src/web/plugin-provider.test.ts": 2, + "tests/unit/agent-tools.test.ts": 2, + "tests/unit/check-gate.test.ts": 2, + "tests/unit/director.test.ts": 2, + "tests/unit/example-agent-plugin.test.ts": 2, + "tests/unit/faremeter.test.ts": 2, + "tests/unit/mcp-client-unwrap.test.ts": 2, + "tests/unit/subagent-session-store.test.ts": 2, + "tests/unit/telemetry-first-run.test.ts": 2, + "tests/unit/tui/approval-reload-during-suspend.test.ts": 2, + "tests/unit/tui/mcp-result-format.test.ts": 2, + "tests/unit/tui/tool-formatter-web-brand.test.ts": 2, + "tests/unit/tui/url-links.test.ts": 2, + "tests/unit/tui/view-render.test.ts": 2, + "tests/unit/tui/view-spec.test.ts": 2, + "evals/compaction/metrics.test.ts": 1, + "src/agent/context-estimate.test.ts": 1, + "src/agent/directors/counsel/package.test.ts": 1, + "src/agent/directors/critic/package.test.ts": 1, + "src/agent/directors/draper/package.test.ts": 1, + "src/agent/directors/explorer/package.test.ts": 1, + "src/agent/directors/gaasbot/package.test.ts": 1, + "src/agent/directors/gauntlet/package.test.ts": 1, + "src/agent/directors/identity.test.ts": 1, + "src/agent/directors/neckbeard/package.test.ts": 1, + "src/agent/directors/prober/package.test.ts": 1, + "src/agent/directors/rand/package.test.ts": 1, + "src/agent/directors/shakespeare/package.test.ts": 1, + "src/agent/directors/tester/package.test.ts": 1, + "src/agent/directors/testsmith/package.test.ts": 1, + "src/agent/directors/tool-sets.test.ts": 1, + "src/agent/directors/warden/package.test.ts": 1, + "src/agent/doom-loop-note.test.ts": 1, + "src/agent/model-family-policy.test.ts": 1, + "src/agent/reactor-events.test.ts": 1, + "src/agent/tool-classification.test.ts": 1, + "src/auth/codex/usage-limit-error.test.ts": 1, + "src/auth/credential-surface.test.ts": 1, + "src/config/xai-providers.test.ts": 1, + "src/cost/cost-visibility.test.ts": 1, + "src/cost/session-cost.test.ts": 1, + "src/exec/runner.test.ts": 1, + "src/mcp/client-auth-retry.test.ts": 1, + "src/mcp/tool-name.test.ts": 1, + "src/plugins/evidence-archive-path-guard.test.ts": 1, + "src/plugins/rg-output.test.ts": 1, + "src/plugins/tool-result-materialize.test.ts": 1, + "src/provider/anthropic-cache-breakpoint.test.ts": 1, + "src/provider/billing-product.test.ts": 1, + "src/provider/openai-compatible-adapter.test.ts": 1, + "src/provider/openai-responses.test.ts": 1, + "src/provider/opencode-go-anthropic-adapter.test.ts": 1, + "src/session/active-host.test.ts": 1, + "src/session/active-run.test.ts": 1, + "src/session/archive-uri.test.ts": 1, + "src/session/attachment-store.test.ts": 1, + "src/session/stream-consumer.test.ts": 1, + "src/subagent/admission.test.ts": 1, + "src/subagent/authority.test.ts": 1, + "src/subagent/fleet-report.ask-wake.test.ts": 1, + "src/subagent/lifecycle.test.ts": 1, + "src/subagent/provider-family.test.ts": 1, + "src/subagent/refresh-inference-source.test.ts": 1, + "src/subagent/run-codex-proxy.test.ts": 1, + "src/subagent/run-suspended-send.test.ts": 1, + "src/subagent/tool-preview.test.ts": 1, + "src/tools/eval-http-env.test.ts": 1, + "src/tui/command-catalog.test.ts": 1, + "src/tui/commands/built-in.test.ts": 1, + "src/tui/deliver-agent-message.test.ts": 1, + "src/tui/dynamic-tool-runner.test.ts": 1, + "src/tui/live-session-port.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/pending-column.test.ts": 1, + "src/tui/prompt-kill-ring.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/runner-exit-code.test.ts": 1, + "src/tui/session-operation-queue.test.ts": 1, + "src/tui/session-queue.test.ts": 1, + "src/tui/stall-watchdog.test.ts": 1, + "src/tui/tool-subject.test.ts": 1, + "src/tui/view/height.test.ts": 1, + "src/tui/view/registry.test.ts": 1, + "src/tui/width-contract.test.ts": 1, + "src/tui/workspace-watch.test.ts": 1, + "src/util/tool-output-uri.test.ts": 1, + "src/web/secret-scrub.test.ts": 1, + "tests/helpers/defined.test.ts": 1, + "tests/unit/codex-providers.test.ts": 1, + "tests/unit/codex-usage.test.ts": 1, + "tests/unit/context-window.test.ts": 1, + "tests/unit/grok-responses-adapter.test.ts": 1, + "tests/unit/mcp-stdio-env.test.ts": 1, + "tests/unit/mcp-tool-name.test.ts": 1, + "tests/unit/mcp-tool-permissions.test.ts": 1, + "tests/unit/openai-responses-adapter.test.ts": 1, + "tests/unit/plugin-register.test.ts": 1, + "tests/unit/provider-protocol-flags.test.ts": 1, + "tests/unit/resolve-inference-spec.test.ts": 1, + "tests/unit/session/run-sink-exec-status.test.ts": 1, + "tests/unit/tui/agent-source-sync.test.ts": 1, + "tests/unit/tui/theme.test.ts": 1, + "tests/unit/workflow-kickoff.test.ts": 1, + "tests/unit/workflows-capabilities.test.ts": 1, + "tests/unit/xai-usage.test.ts": 1, + "src/agent/live-tool-dispatch.test.ts": 0, + "src/config/oauth-providers.test.ts": 0, + "src/config/session-mode.test.ts": 0, + "src/inference-abort.test.ts": 0, + "src/mcp/tool-permissions.test.ts": 0, + "src/plugins/origin-marker.test.ts": 0, + "src/plugins/tool-time-budget.test.ts": 0, + "src/provider/bifrost-adapter.test.ts": 0, + "src/tui/exit-command.test.ts": 0, + "src/tui/sent-message-history.test.ts": 0, + "tests/unit/inference-abort.test.ts": 0, + "tests/unit/run-agent.test.ts": 0, + "tests/unit/telemetry-singleton.test.ts": 0, + "tests/unit/workflows-registry.test.ts": 0 + } +} From 20c2407e064ade584954358f75ff1e1fbd01df84 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 13 Sep 2026 23:22:40 -0700 Subject: [PATCH 2/3] fix(ci): cover time-balanced shards in the gate test --- tests/unit/check-gate.test.ts | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/unit/check-gate.test.ts b/tests/unit/check-gate.test.ts index 73018d997..efcfa49ff 100644 --- a/tests/unit/check-gate.test.ts +++ b/tests/unit/check-gate.test.ts @@ -67,14 +67,28 @@ describe("check gate", () => { }); test("CI test shards cover exactly the suite's paths", () => { - // Sharding must never silently drop part of the suite: the union of the - // matrix shards has to equal the unsharded `test` script's paths. - const shardPaths = [...ci.matchAll(/^\s+paths: (.+)$/gm)] - .flatMap((match) => match[1]?.trim().split(/\s+/) ?? []) - .sort(); + // Time-balanced --shard slices each run the full union (bun splits by + // file, balanced by --timings), so coverage holds when the matrix has + // all four slices and the shard command template carries the suite's + // paths plus the interpolated --shard flag. Sharding must never + // silently drop part of the suite. const suitePaths = TEST_SUITE.split(" ") .filter((part) => part.startsWith("./")) .sort(); - expect(shardPaths).toEqual(suitePaths); + for (const shard of ["1/4", "2/4", "3/4", "4/4"]) { + expect(ci).toContain(`"${shard}"`); + } + const runArgs = [ + ...ci.matchAll(/^\s*run: bun run check:projects-dir-guard(.+)$/gm), + ] + .map((match) => match[1] ?? "") + .find((args) => args.includes("--shard=${{ matrix.shard }}")); + expect(runArgs).toBeDefined(); + const runPaths = (runArgs ?? "") + .split(/\s+/) + .filter((part) => part.startsWith("./")) + .sort(); + expect(runPaths).toEqual(suitePaths); + expect(runArgs).toContain("--timings=./scripts/ci-timings.json"); }); }); From f3bdd32f057e9ea77ffba22402d91b99b81ee488 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 13 Sep 2026 23:29:43 -0700 Subject: [PATCH 3/3] ci(test): run the pollution detector nightly, not per-PR --- .github/workflows/ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f70cc0818..94d1acaa3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,9 @@ on: branches: [main] pull_request: workflow_dispatch: + # Nightly pollution-detector run (see test-one-process below). + schedule: + - cron: "17 7 * * *" concurrency: group: ci-${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }} @@ -152,10 +155,14 @@ jobs: # files fails in the one-process suite yet passes when the files land in # different shards (CL-6967). This job reruns the whole union in one # process with no filters, exactly like the local `bun run check` gate. - # Non-blocking (continue-on-error) so a slow or flaky full run cannot hold - # the gate; a real pollution failure still shows up red for triage. + # Nightly (plus manual workflow_dispatch), not per-PR: the one-process + # suite takes ~2 minutes on CI and would put that back on the PR wall + # clock this sharding removes. Non-blocking (continue-on-error) so a slow + # or flaky full run cannot hold the gate; a real pollution failure still + # shows up red for triage. test-one-process: name: test (one-process pollution detector) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' continue-on-error: true runs-on: ubuntu-latest steps: