Skip to content

Run every test by discovery, not by an allowlist (Fixes #2979) - #3103

Merged
acoliver merged 6 commits into
mainfrom
issue2979
Aug 6, 2026
Merged

Run every test by discovery, not by an allowlist (Fixes #2979)#3103
acoliver merged 6 commits into
mainfrom
issue2979

Conversation

@acoliver

@acoliver acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

TLDR

The shared Bun test runner decided which test files to execute from a hand-maintained list in scripts/bun-test-manifest.ts. That list had drifted: 43 test files existed on disk that no CI job ever ran. They looked like coverage and asserted nothing, and two of them had rotted into real failures nobody could see.

This deletes the manifest and makes selection structural. scripts/bun-test-roots.ts declares only how a root runs (cwd, scanned directories, filename pattern, preloads, tsconfig, timeout, retries, globalSetup, credentials, per-file timeout overrides). Which files run is answered by walking the filesystem, so a new test file executes by virtue of existing. There is no files, include, or exclude member anywhere in the selection path — the issue's hard requirement.

scripts/check-test-file-coverage.ts is the new guard that keeps this honest: it walks the repository for test files and fails when one is executed by no executor or by more than one. It runs on every non-docs PR, which is what lets the bun_native_test_parity job be deleted.

Turning discovery on immediately paid for itself: it exposed a Linux-only Bun fake-timer hang that the deleted allowlist had been commenting out for seven packages/providers auth suites, and that fix took the providers shard from 16m09s to 6m23s on CI.

Reviewer attention: the agents root narrowing to test-bun, the removal of the cli/core roots, and the migration of lsp onto the shared runner all reduce what the shared runner claims. The coverage guard is the mechanism that proves nothing fell through those gaps — please satisfy yourself it actually does.

Dive Deeper

Why a canary job was not the answer

bun_native_test_parity only ran --dry-run: it proved every listed file existed. It could never prove the list was complete, which is precisely how 43 files went unexecuted. The replacement guard asserts completeness instead, and derives the covered set from each executor's own discovery code rather than restating it, so it cannot drift out of sync with what CI runs.

Fail fast, not silently

The walker previously would have swallowed readDirectory/stat/realpath failures and returned a short list — an unreadable subdirectory could drop tests while another file kept the root non-empty, so the "no files found" check never fired. All three now propagate as BunTestRootStatError. Related fixes in the same vein:

  • Dot-prefixed directories are still pruned (that is what excludes .git/, .github/, .integration-tests/), but a dot-prefixed file matching the pattern is no longer silently dropped.
  • Discovered files are deduplicated by real path, so a symlink alias of a scanned directory cannot execute one real file twice.

The guard has to run on the PR that would break it

Affected-shard selection maps a package-test-only change to that package's shard, so a PR adding packages/providers/src/new.test.ts selects [providers] and skips scripts. A guard living only in the scripts shard would therefore be skipped on exactly the PR that could introduce an uncovered file. scripts/check-test-file-coverage.ts is now directly executable (npm run lint:test-file-coverage) and wired as a step in the always-run bun_test_orchestrator_smoke job. The behavioral bun:test suite is kept as well.

Consequences of actually running everything

Change Detail
providers +41, tools +1, storage +1 The previously-unexecuted files. Three failed; all three are fixed, none skipped or excluded.
cli and core shared roots deleted Their bespoke run-bun-tests.ts runners already discover every file with no allowlist. Redundant.
agents shared root narrowed to test-bun Its four src/** entries were being executed twice per CI run — once by the shared root, once by the bespoke runner.
core and auth broadened to *.spec Their matchers only caught *.test, so 13 spec files ran nowhere. All 13 pass. JUnit classnames strip the suffix accordingly.
lsp moved onto the shared runner Its bare bun test could only be modelled by the guard, never derived — a hole in the guarantee. All 13 lsp files pass under one-process-per-file.
scripts-tests-slow folded away Replaced by a per-file timeoutOverrides entry. An override changes the budget only, never membership (300s per-test / 600s process, matching the deleted root).

The three previously-invisible failures

File Root cause Side fixed
providers/src/openai/OpenAIRequestPreparation.issue2853.test.ts vi.mock('../../prompt-config/subagent-delegation.js') resolves to nothing; production imports @vybestack/llxprt-code-core/prompt-config/subagent-delegation.js, which is what every executed sibling test mocks. Test
providers/src/runtime/promptEnvelopeProjections.test.ts Bun's toMatchObject mutates its received object when resolving asymmetric matchers, unfreezing it; the Object.isFrozen assertion ran after it. Product freezing is correct and still asserted, now before the matcher. Test
tools/src/tools/check-async-tasks-shell-formatter.test.ts Expected 2023-11-14T22:14:10.000Z but new Date(1_700_000_005_000).toISOString() is 2023-11-14T22:13:25.000Z — the expectation, not the formatter, was wrong. Test

Bespoke runner changes

packages/{core,agents,auth}/run-bun-tests.ts gain import.meta.main guards and export the same discoverTestFiles function main() itself calls, so the guard reads exactly what CI executes rather than a copy. core and auth also now await main() — an unawaited async main() turns a discovery failure into an unhandled rejection instead of a clean non-zero exit.

scripts/tests/bun-test-root-ownership.bun.test.ts was extended to read the real package.json files and prove each bespoke executor's workspace test script still invokes its runner, so the executor table cannot silently claim coverage a package no longer provides.

Reviews performed

DeepThinker (3 blockers, 4 should-fix) and Open Code Review (7 findings) were run against this change; every finding is resolved in the branch. The blockers were the swallowed filesystem errors, the dot-file pruning, and the guard not running under affected-shard selection.

The blocker the allowlist was hiding

The seven suites the manifest had commented out carried the note "Bun fake-timer incompatibility on Linux CI ... re-add when Bun runtime is fixed." Every case in them timed out at exactly the per-test timeout on Linux while passing on macOS. Reproduced in oven/bun:1.3.14 on linux/arm64:

test-setup/augment-bun-vi.ts's flushPendingTasks ended its microtask drain by awaiting a setImmediate. Under Bun's fake timers, once a timer has fired and the clock is then advanced with no pending timers, setImmediate (and setTimeout(_, 0)) is gated by the fake-timer scheduler and never becomes due — so the await never returns and advanceTimersByTimeAsync hangs until teardown. Probes in that state show queueMicrotask, process.nextTick and Promise.resolve all returning in under a millisecond, because microtasks drain inside the current macrotask before the scheduler regains control. macOS happens to keep firing the macrotask, which is the whole reason the failure looked platform-specific.

The settling boundary is now a microtask. That also restored Vitest parity the macrotask had broken: for a timer whose awaited continuation schedules a nested timer, Vitest fires it at 25ms and the shim's own test asserted 35ms — the extra macrotask turn had deferred the continuation past the next timer. Confirmed by running the identical scenario under Vitest, which produces 25.

Two more failures this surfaced, both pre-existing

  • scripts/tests/ocr-concurrency-canary-2673.test.ts asserted the client observes a 200 after the upstream destroys the socket mid-body. The proxy forwards the 200 and then tears the client connection down with response.destroy(), which sends an RST; on Linux an RST discards data still unread in the client's receive buffer, so the status line may never arrive. This failed the scripts shard on main (run 31071511528) before this branch existed. Reproduced 1-in-12 in a linux/amd64 Node 24 container. The three telemetry assertions — one request, no upstream errors, one forwarded 200 — are unchanged and remain the proof of the behavior the test is named for; only the client-side assertion now accepts either outcome the RST race can produce, and names the observed one so an unexpected third outcome reports its status and body.
  • packages/agents fails a different file on each run and passes in isolation. Reproduced on main with this branch stashed (four files failed there). Not addressed here.

Review findings folded in

Two soundness gaps were found reviewing the discovery work itself and are fixed in this PR:

  • The core/agents/auth runners discovered under import.meta.dir while the spawned child, its preload and the JUnit report still resolved against process.cwd(). The anchors agreed only when the runner was invoked from its own package directory. Each now anchors everything at one WORKSPACE_ROOT constant.
  • The coverage guard compared lexical paths, so a test reached through a symlink alias could be reported as uncovered on one side while a genuine duplicate went unnoticed on the other — undermining the exactly-once guarantee the guard exists to prove. Both the repository walk and the executor claims now canonicalize, and a two-executors-one-directory symlink fixture asserts it.

Not in scope

Migrating remaining workspaces (#2845/#2846/#2847), removing Vitest (#2970), rewriting the vitest specifier (#2969), and re-recording CI critical-path timings in #2702 (a post-merge measurement, not a code change).

Reviewer Test Plan

  1. Prove the allowlist is gone. grep -rn "files:\|include:\|exclude:" scripts/bun-test-roots.ts returns nothing in the root table. The new suite asserts this mechanically.
  2. Prove discovery works without config edits.
    touch packages/providers/src/zz-scratch.test.ts && bun scripts/run_bun_tests.ts --root providers --dry-run | grep zz-scratch — it appears. Delete it afterwards.
  3. Prove the guard catches an orphan.
    mkdir -p research/orphan && touch research/orphan/x.test.ts && bun scripts/check-test-file-coverage.ts — should fail and name the file. Delete it afterwards.
  4. Prove the guard catches a double. Temporarily add directories: ['src', 'test-bun'] back to a root whose bespoke runner also scans src and re-run the guard — it should report the file with both claiming executors.
  5. Prove fail-fast. chmod 000 a subdirectory under a scanned root and run bun scripts/run_bun_tests.ts --root <that root> --dry-run — it must raise BunTestRootStatError, not quietly return fewer files. Restore permissions afterwards.
  6. Run the affected suites: bun test scripts/tests/bun-test-roots.bun.test.ts scripts/tests/test-file-coverage.bun.test.ts scripts/tests/bun-test-root-ownership.bun.test.ts, then cd packages/lsp && npm test, cd packages/core && npm test, cd packages/auth && npm test.
  7. Confirm the previously-omitted files run and pass: bun scripts/run_bun_tests.ts --root providers (544 files, was 503).

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Verified locally on macOS: npm run format, npm run typecheck, npm run lint, npm run build, npm run test, and the CLI smoke (bun scripts/start.ts --profile-load stepfun-37). Linux is covered by CI. The change is filesystem-walk based, so the Windows path-separator handling is worth a reviewer's eye.

Residual npm run test failures on this machine are pre-existing local flakes: packages/agents and packages/core fail a different file on each run and pass in isolation (reproduced on main with the branch stashed, which failed four files), and the three packages/cli integration failures are in a workspace this PR does not touch (git diff HEAD -- packages/cli is empty).

Linked issues / bugs

Fixes #2979

Contributes to #2578 (finish the Bun migration) and #2702 (CI execution optimization). Same silent-exclusion class as #2923. Precedes #2970.

Summary by CodeRabbit

  • New Features

    • Bun test discovery now supports .spec.ts and .spec.tsx files.
    • Test files are automatically discovered across configured project roots.
    • Added coverage checks to detect skipped or duplicate test execution.
  • Bug Fixes

    • Improved asynchronous timer test reliability.
    • Updated timing and timestamp expectations.
    • Improved resilience of proxy failure tests.
  • CI

    • Continuous integration now verifies complete, non-duplicated test coverage.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f9fb9f5-b4ac-4713-afa5-bf98f0822534

📥 Commits

Reviewing files that changed from the base of the PR and between 904a0d0 and 3025f13.

⛔ Files ignored due to path filters (3)
  • dev-docs/bun.md is excluded by !dev-docs/**
  • dev-docs/test-runner-inventory.md is excluded by !dev-docs/**
  • project-plans/issue2979/plan.md is excluded by !project-plans/**
📒 Files selected for processing (37)
  • .github/workflows/ci.yml
  • package.json
  • packages/agents/run-bun-tests.ts
  • packages/auth/run-bun-tests.ts
  • packages/cli/bunfig.toml
  • packages/cli/src/ui/components/AuthDialog.test.tsx
  • packages/core/run-bun-tests.ts
  • packages/lsp/package.json
  • packages/providers/src/openai/OpenAIRequestPreparation.issue2853.test.ts
  • packages/providers/src/runtime/promptEnvelopeProjections.test.ts
  • packages/tools/src/tools/check-async-tasks-shell-formatter.test.ts
  • scripts/bun-test-manifest-data-mcp.ts
  • scripts/bun-test-manifest-data-providers.ts
  • scripts/bun-test-manifest-data-storage.ts
  • scripts/bun-test-manifest-data-tools.ts
  • scripts/bun-test-manifest-validation.ts
  • scripts/bun-test-manifest.ts
  • scripts/bun-test-roots.ts
  • scripts/check-affected-test-shards.ts
  • scripts/check-test-file-coverage.ts
  • scripts/run_bun_tests.ts
  • scripts/test.ts
  • scripts/tests/bun-test-manifest.bun.test.ts
  • scripts/tests/bun-test-root-ownership.bun.test.ts
  • scripts/tests/bun-test-roots.bun.test.ts
  • scripts/tests/ci-docs-only-skip.bun.test.ts
  • scripts/tests/issue-2994-lint-scoped.bun.test.ts
  • scripts/tests/issue-planner-confinement.bun.test.ts
  • scripts/tests/issue-planner-enrichment.bun.test.ts
  • scripts/tests/ocr-review-workflow.bun.test.ts
  • scripts/tests/pr-review-walkthrough-sanitize.bun.test.ts
  • scripts/tests/run_bun_tests.subprocess.test.ts
  • scripts/tests/test-file-coverage.bun.test.ts
  • scripts/tests/test-shard-orchestrator.test.ts
  • test-setup/augment-bun-vi.test.ts
  • test-setup/augment-bun-vi.ts
  • tsconfig.scripts.json
📝 Walkthrough

Walkthrough

Changes

The pull request replaces Bun test manifests with filesystem-discovered test roots. It adds executor coverage validation, updates Bun runners and CI wiring, removes obsolete manifest references, and adjusts tests for current runtime behavior.

Bun test discovery and execution

Layer / File(s) Summary
Test-root configuration and resolution
scripts/bun-test-roots.ts, scripts/tests/bun-test-roots.bun.test.ts, scripts/tests/bun-test-root-ownership.bun.test.ts
Configured roots now support recursive discovery, symlink protection, credential filtering, configuration validation, timeout overrides, and sorted resolved test files.
Manifest replacement in the shared runner
scripts/run_bun_tests.ts, scripts/tests/run_bun_tests.subprocess.test.ts, scripts/tests/ocr-review-workflow.bun.test.ts, scripts/check-affected-test-shards.ts, tsconfig.scripts.json
The shared runner resolves files from configured roots instead of the deleted manifest. Related tests, comments, and TypeScript project inputs use the new module.
Package runner discovery adapters
packages/agents/run-bun-tests.ts, packages/auth/run-bun-tests.ts, packages/core/run-bun-tests.ts, packages/lsp/package.json
Package runners export discovery helpers, support .spec.ts and .spec.tsx, anchor execution paths to each workspace, and guard entry-point startup.
Executor coverage guard
scripts/check-test-file-coverage.ts, scripts/tests/test-file-coverage.bun.test.ts, package.json, .github/workflows/ci.yml
The new guard reports uncovered and multiply executed test files. It runs through an npm script and the CI orchestrator smoke step.
Scripts shard orchestration
scripts/test.ts, scripts/tests/test-shard-orchestrator.test.ts
Scripts roots run in separate invocations with independent timeout handling and fail-fast behavior.

Test maintenance updates

Layer / File(s) Summary
Test behavior and fixture updates
packages/providers/src/..., packages/tools/src/tools/check-async-tasks-shell-formatter.test.ts, scripts/tests/ocr-concurrency-canary-2673.test.ts, packages/cli/src/ui/components/AuthDialog.test.tsx
Tests now use updated module paths, assert immutability before matching, expect the current timestamp, accept forwarded or connection-error outcomes, and poll asynchronous UI callbacks.
Fake-timer settling boundary
test-setup/augment-bun-vi.ts, test-setup/augment-bun-vi.test.ts
flushPendingTasks now settles with queueMicrotask after bounded promise draining, with updated timer-order expectations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • #2702: The change modifies Bun test execution and CI orchestration as part of CI optimization work.

Possibly related PRs

Suggested labels: ci/cd, Code Quality / Modularization

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Most coding requirements are met, but the PR does not clearly confirm that telemetry and CLI manifest coverage remains covered or redundant. Document how telemetry and CLI coverage remains covered, or preserve equivalent executor coverage before merging.
Out of Scope Changes check ⚠️ Warning The OCR concurrency assertion and AuthDialog polling change are not directly related to replacing Bun manifest discovery. Move the unrelated OCR and AuthDialog test changes to a separate pull request or document their direct dependency on this migration.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: replacing the Bun test allowlist with filesystem discovery.
Description check ✅ Passed The description includes all template sections, explains the design and testing, and reports verification status.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue2979

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR changes 41 file(s).

  • scripts/tests/issue-planner-enrichment.bun.test.ts: (per-file summary unavailable)
  • package.json: (per-file summary unavailable)
  • packages/cli/src/ui/components/AuthDialog.test.tsx: (per-file summary unavailable)
  • scripts/tests/issue-planner-confinement.bun.test.ts: (per-file summary unavailable)
  • scripts/run_bun_tests.ts: (per-file summary unavailable)
  • scripts/tests/test-file-coverage.bun.test.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest-data-providers.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest-data-tools.ts: (per-file summary unavailable)
  • .github/workflows/ci.yml: (per-file summary unavailable)
  • packages/agents/run-bun-tests.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest.ts: (per-file summary unavailable)
  • scripts/tests/pr-review-walkthrough-sanitize.bun.test.ts: (per-file summary unavailable)
  • scripts/tests/bun-test-roots.bun.test.ts: (per-file summary unavailable)
  • scripts/test.ts: (per-file summary unavailable)
  • packages/providers/src/runtime/promptEnvelopeProjections.test.ts: (per-file summary unavailable)
  • scripts/check-affected-test-shards.ts: (per-file summary unavailable)
  • scripts/tests/bun-test-root-ownership.bun.test.ts: (per-file summary unavailable)
  • packages/core/run-bun-tests.ts: (per-file summary unavailable)
  • project-plans/issue2979/plan.md: (per-file summary unavailable)
  • scripts/bun-test-manifest-validation.ts: (per-file summary unavailable)
  • packages/cli/bunfig.toml: (per-file summary unavailable)
  • test-setup/augment-bun-vi.test.ts: (per-file summary unavailable)
  • packages/providers/src/openai/OpenAIRequestPreparation.issue2853.test.ts: (per-file summary unavailable)
  • dev-docs/test-runner-inventory.md: (per-file summary unavailable)
  • scripts/tests/test-shard-orchestrator.test.ts: (per-file summary unavailable)
  • scripts/tests/bun-manifest-root-ownership.bun.test.ts: (per-file summary unavailable)
  • scripts/bun-test-roots.ts: (per-file summary unavailable)
  • scripts/tests/issue-2994-lint-scoped.bun.test.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest-data-storage.ts: (per-file summary unavailable)
  • tsconfig.scripts.json: (per-file summary unavailable)
  • packages/tools/src/tools/check-async-tasks-shell-formatter.test.ts: (per-file summary unavailable)
  • packages/auth/run-bun-tests.ts: (per-file summary unavailable)
  • scripts/tests/ocr-review-workflow.bun.test.ts: (per-file summary unavailable)
  • packages/lsp/package.json: (per-file summary unavailable)
  • scripts/tests/ci-docs-only-skip.bun.test.ts: (per-file summary unavailable)
  • dev-docs/bun.md: (per-file summary unavailable)
  • test-setup/augment-bun-vi.ts: (per-file summary unavailable)
  • scripts/tests/bun-test-manifest.bun.test.ts: (per-file summary unavailable)
  • scripts/tests/run_bun_tests.subprocess.test.ts: (per-file summary unavailable)
  • scripts/check-test-file-coverage.ts: (per-file summary unavailable)
  • scripts/bun-test-manifest-data-mcp.ts: (per-file summary unavailable)

Changes

Layer File(s) Summary
scripts/tests scripts/tests/issue-planner-enrichment.bun.test.ts, scripts/tests/issue-planner-confinement.bun.test.ts, scripts/tests/test-file-coverage.bun.test.ts, scripts/tests/pr-review-walkthrough-sanitize.bun.test.ts, scripts/tests/bun-test-roots.bun.test.ts, scripts/tests/bun-test-root-ownership.bun.test.ts, scripts/tests/test-shard-orchestrator.test.ts, scripts/tests/bun-manifest-root-ownership.bun.test.ts, scripts/tests/issue-2994-lint-scoped.bun.test.ts, scripts/tests/ocr-review-workflow.bun.test.ts, scripts/tests/ci-docs-only-skip.bun.test.ts, scripts/tests/bun-test-manifest.bun.test.ts, scripts/tests/run_bun_tests.subprocess.test.ts Changes in scripts/tests
. package.json, tsconfig.scripts.json Changes in .
packages/cli/src/ui/components packages/cli/src/ui/components/AuthDialog.test.tsx Changes in packages/cli/src/ui/components
scripts scripts/run_bun_tests.ts, scripts/bun-test-manifest-data-providers.ts, scripts/bun-test-manifest-data-tools.ts, scripts/bun-test-manifest.ts, scripts/test.ts, scripts/check-affected-test-shards.ts, scripts/bun-test-manifest-validation.ts, scripts/bun-test-roots.ts, scripts/bun-test-manifest-data-storage.ts, scripts/check-test-file-coverage.ts, scripts/bun-test-manifest-data-mcp.ts Changes in scripts
.github/workflows .github/workflows/ci.yml Changes in .github/workflows
packages/agents packages/agents/run-bun-tests.ts Changes in packages/agents
packages/providers/src/runtime packages/providers/src/runtime/promptEnvelopeProjections.test.ts Changes in packages/providers/src/runtime
packages/core packages/core/run-bun-tests.ts Changes in packages/core
project-plans/issue2979 project-plans/issue2979/plan.md Changes in project-plans/issue2979
packages/cli packages/cli/bunfig.toml Changes in packages/cli
test-setup test-setup/augment-bun-vi.test.ts, test-setup/augment-bun-vi.ts Changes in test-setup
packages/providers/src/openai packages/providers/src/openai/OpenAIRequestPreparation.issue2853.test.ts Changes in packages/providers/src/openai
dev-docs dev-docs/test-runner-inventory.md, dev-docs/bun.md Changes in dev-docs
packages/tools/src/tools packages/tools/src/tools/check-async-tasks-shell-formatter.test.ts Changes in packages/tools/src/tools
packages/auth packages/auth/run-bun-tests.ts Changes in packages/auth
packages/lsp packages/lsp/package.json Changes in packages/lsp

Magnitude

🎯 5 (XXL)
2577 additions, 2138 deletions, 40 changed files across 7 packages, 54 acceptance criteria

Related

No related items found.


Walkthrough generated by LLxprt PR Review. Planner issue: #2256

Comment thread scripts/tests/bun-test-root-ownership.bun.test.ts
Comment thread packages/auth/run-bun-tests.ts
Comment thread scripts/bun-test-roots.ts Outdated
Comment thread scripts/check-test-file-coverage.ts
Comment thread scripts/check-test-file-coverage.ts
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

OpenCodeReview — automatic reviews suspended

Automatic OCR reviews are suspended for this PR after 2 of 2 automatic reviews.

To get more reviews you can:

  • Check the box below to re-enable automatic reviews (resets the counter), or

  • Comment /review, /ocr, or /open-code-review to request a single review on demand.

  • Re-enable automatic reviews

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Not reproducible against the code as written — scriptsRunning filters the array of workspace test scripts and returns the matching scripts, not the matching occurrences within one script.

For agents, packages/agents/package.json test is a single string:

bun run-bun-tests.ts && bun ../../scripts/run_bun_tests.ts --workspace agents

That is one element of executingScripts(). It satisfies both predicates once, so scriptsRunning('agents') returns a one-element array, not two.

For providers, the script is:

bun ../../scripts/run_bun_tests.ts --workspace providers --junit junit.xml

The regex is (?:--workspace|--root|-w)[= ]providers(?:\s|$), and --workspace providers matches (the trailing space satisfies \s), so it returns one, not zero.

The suite asserts exactly this for every non-credentialed root in BUN_TEST_ROOTS and passes 22/22 locally and in the scripts shard on CI, which it could not do if agents returned 2 or providers returned 0.

The underlying concern — that the executor table could claim coverage a package no longer provides — is real and is covered separately in this same file by the bespoke-runner wiring assertions added below, which read each workspace's real package.json and require its test script to invoke run-bun-tests.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Not reproducible — findTestFiles in packages/auth/run-bun-tests.ts does skip those entries. Lines 25-36:

for (const entry of readdirSync(dir)) {
  const fullPath = join(dir, entry);
  if (
    entry === 'dist' ||
    entry === 'node_modules' ||
    entry === 'coverage' ||
    entry.startsWith('.')
  ) {
    continue;
  }

The docblock matches the implementation. This guard predates the PR; the only change here was broadening the file matcher to also accept *.spec.ts / *.spec.tsx (and updating the docblock to say so), which brought nine previously-unexecuted auth spec files into the suite.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Correct, and fixed. The pattern was documented as a "filename pattern" while resolveTimeoutForFile tests it against the resolved absolute path, so a basename-anchored pattern such as /^slow\.test\.ts$/ would silently never match.

Matching against the absolute path is the behavior worth keeping — it lets an override scope itself to a directory rather than to a bare name, which a monorepo needs. So the contract is now documented rather than changed. BunTestTimeoutOverride.pattern says:

Matched against the resolved ABSOLUTE path of a discovered file, not its basename, so a pattern may scope itself to a directory. Anchor the end (/name\.test\.ts$/) rather than the start when targeting one file.

BunTestRoot.timeoutOverrides and the field table in dev-docs/bun.md now say "absolute-path pattern", and the docblock records that the first matching entry wins. The one override in the tree already anchors the end correctly.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Declining both of the try/catch suggestions on scripts/check-test-file-coverage.ts (the one on analyzeTestFileCoverage/collectExecutorClaims and the one on main), because they would undo the change this PR just made deliberately.

This repository's stated architecture preference is fail fast over defense in depth. More specifically, the previous review round on this PR classified the opposite behavior as a blocker: the discovery walker used to swallow readDirectory/stat failures and return a short list, so an unreadable subdirectory could drop test files while another file kept the root non-empty and the "no files found" check never fired. The guard reused that walker, so it could report "zero uncovered" over an incomplete inventory. The fix was to make those failures propagate as BunTestRootStatError. Catching them again in the guard would restore exactly the blind spot that made 43 test files invisible in the first place.

On the specific cases raised:

  • A root that discovers no test files is not noise to be reported cleanly and moved past. It means a configured root is scanning nothing, which is a real misconfiguration and must fail the guard.
  • "One failing executor prevents coverage analysis for all others" is the intended behavior. If an executor cannot enumerate what it runs, the covered set is unknown, and any "uncovered" answer computed from a partial union is untrustworthy. Reporting a partial result would be worse than failing.
  • Log readability — an uncaught throw already exits non-zero with the failing path and its error code in BunTestRootStatError's message. That is diagnosable; the stack trace is the useful part, not noise.

The clean-message path is reserved for the findings the guard is designed to report (uncovered files, doubly-executed files), which main prints explicitly before exiting 1. An inability to compute those findings is a different failure and is deliberately louder.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
packages/core/run-bun-tests.ts (1)

62-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting the shared discovery helper.

discoverTestFiles and findTestFiles are now near-identical in packages/core/run-bun-tests.ts, packages/auth/run-bun-tests.ts, and packages/agents/run-bun-tests.ts. Only TEST_ROOTS and the pruned-directory handling differ. The three copies must stay in agreement because scripts/check-test-file-coverage.ts treats each as an authoritative executor claim. A shared helper parameterized by roots and pruned directories would remove the drift risk.

This is a cross-package extraction, so it can be deferred to a follow-up.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/run-bun-tests.ts` around lines 62 - 77, Defer this
cross-package refactor as a follow-up; leave discoverTestFiles and findTestFiles
unchanged for now.
scripts/tests/bun-test-roots.bun.test.ts (1)

285-300: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider guarding the symlink fixture on Windows.

symlinkSync commonly throws EPERM on unprivileged Windows. Sibling suites under scripts/tests guard such fixtures with describe.skipIf(process.platform === 'win32'). CI runs this suite on macOS only, so this affects local Windows development rather than CI.

Based on learnings: for test files under scripts/tests that use real filesystem symlinks, guard these tests with describe.skipIf(process.platform === 'win32').

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/bun-test-roots.bun.test.ts` around lines 285 - 300, Guard the
symlink-cycle test containing symlinkSync in a describe.skipIf(process.platform
=== 'win32') block, following the existing pattern in scripts/tests. Keep the
test behavior unchanged on non-Windows platforms.

Source: Learnings

scripts/bun-test-roots.ts (1)

546-555: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider failing loudly on an unknown rootFilter.

resolveBunTestFiles returns an empty array when rootFilter matches no entry in BUN_TEST_ROOTS. A typo in --workspace then produces the generic "No native Bun test files found" message in scripts/run_bun_tests.ts line 645, which does not distinguish a misspelled root from a root that legitimately resolved zero files. Every other failure mode in this module fails loudly with a specific message. An explicit unknown-root error would keep that property.

Note: scripts/tests/bun-test-roots.bun.test.ts line 512-514 currently codifies the empty-array behavior, so this change requires a matching test update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/bun-test-roots.ts` around lines 546 - 555, The resolveBunTestFiles
function should reject an explicitly provided rootFilter when it matches no
entry in BUN_TEST_ROOTS, raising a specific unknown-root error instead of
returning an empty array; preserve empty results for valid roots that resolve no
files, and update the corresponding bun-test-roots test to expect the new
failure.
scripts/tests/run_bun_tests.subprocess.test.ts (1)

166-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep subprocess coverage for --preload propagation.

Both subprocess runner cases use test-setup, whose BUN_TEST_ROOTS entry declares no preload. Keep one case that targets a root with declared preloads, or add an equivalent subprocess assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/tests/run_bun_tests.subprocess.test.ts` at line 166, Update the
subprocess runner tests around the `test-setup` cases to retain coverage for
`--preload` propagation: ensure at least one subprocess case targets a test root
whose `BUN_TEST_ROOTS` configuration declares preloads, or add an equivalent
assertion while preserving the existing runner coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/agents/run-bun-tests.ts`:
- Around line 363-364: The test runners use inconsistent path anchors between
discovery and child execution. In packages/agents/run-bun-tests.ts:363-364,
packages/auth/run-bun-tests.ts:153-154, and
packages/core/run-bun-tests.ts:164-165, preserve absolute paths from
import.meta.dir when calling runTestFile or configure the spawned child cwd to
root; additionally resolve each PRELOAD and the agents junit.xml output against
root so all execution and output paths share the discovery anchor.

In `@scripts/check-test-file-coverage.ts`:
- Around line 153-170: Update collectExecutorClaims to canonicalize each
discovered executor file with realpathSync before adding it to files or
recording it in counts, ensuring symlink aliases share one coverage identity.
Add a symlink-based fixture or test covering a real file discovered through an
alias and verify it produces one canonical claim.

In `@scripts/tests/bun-test-roots.bun.test.ts`:
- Around line 583-586: Update the ordinary-file assertion in the bun test root
checks to first verify that the find result for run_bun_tests.test.ts is
defined, then assert its timeout is undefined. Keep the existing timeout
expectation while preventing a missing file from passing vacuously.

In `@test-setup/augment-bun-vi.ts`:
- Around line 132-136: Update the comment describing the bounded microtask-drain
loop to replace “arbitrarily deep” with “up to MICROTASK_DRAIN_ROUNDS rounds,”
and explicitly state that callbacks requiring additional rounds may remain
pending.

---

Nitpick comments:
In `@packages/core/run-bun-tests.ts`:
- Around line 62-77: Defer this cross-package refactor as a follow-up; leave
discoverTestFiles and findTestFiles unchanged for now.

In `@scripts/bun-test-roots.ts`:
- Around line 546-555: The resolveBunTestFiles function should reject an
explicitly provided rootFilter when it matches no entry in BUN_TEST_ROOTS,
raising a specific unknown-root error instead of returning an empty array;
preserve empty results for valid roots that resolve no files, and update the
corresponding bun-test-roots test to expect the new failure.

In `@scripts/tests/bun-test-roots.bun.test.ts`:
- Around line 285-300: Guard the symlink-cycle test containing symlinkSync in a
describe.skipIf(process.platform === 'win32') block, following the existing
pattern in scripts/tests. Keep the test behavior unchanged on non-Windows
platforms.

In `@scripts/tests/run_bun_tests.subprocess.test.ts`:
- Line 166: Update the subprocess runner tests around the `test-setup` cases to
retain coverage for `--preload` propagation: ensure at least one subprocess case
targets a test root whose `BUN_TEST_ROOTS` configuration declares preloads, or
add an equivalent assertion while preserving the existing runner coverage.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c05f8ad-0678-450e-86f5-d4b311cb8c41

📥 Commits

Reviewing files that changed from the base of the PR and between 42ca2a9 and d93b78d.

⛔ Files ignored due to path filters (3)
  • dev-docs/bun.md is excluded by !dev-docs/**
  • dev-docs/test-runner-inventory.md is excluded by !dev-docs/**
  • project-plans/issue2979/plan.md is excluded by !project-plans/**
📒 Files selected for processing (36)
  • .github/workflows/ci.yml
  • package.json
  • packages/agents/run-bun-tests.ts
  • packages/auth/run-bun-tests.ts
  • packages/core/run-bun-tests.ts
  • packages/lsp/package.json
  • packages/providers/src/openai/OpenAIRequestPreparation.issue2853.test.ts
  • packages/providers/src/runtime/promptEnvelopeProjections.test.ts
  • packages/tools/src/tools/check-async-tasks-shell-formatter.test.ts
  • scripts/bun-test-manifest-data-mcp.ts
  • scripts/bun-test-manifest-data-providers.ts
  • scripts/bun-test-manifest-data-storage.ts
  • scripts/bun-test-manifest-data-tools.ts
  • scripts/bun-test-manifest-validation.ts
  • scripts/bun-test-manifest.ts
  • scripts/bun-test-roots.ts
  • scripts/check-affected-test-shards.ts
  • scripts/check-test-file-coverage.ts
  • scripts/run_bun_tests.ts
  • scripts/test.ts
  • scripts/tests/bun-test-manifest.bun.test.ts
  • scripts/tests/bun-test-root-ownership.bun.test.ts
  • scripts/tests/bun-test-roots.bun.test.ts
  • scripts/tests/ci-docs-only-skip.bun.test.ts
  • scripts/tests/issue-2994-lint-scoped.bun.test.ts
  • scripts/tests/issue-planner-confinement.bun.test.ts
  • scripts/tests/issue-planner-enrichment.bun.test.ts
  • scripts/tests/ocr-concurrency-canary-2673.test.ts
  • scripts/tests/ocr-review-workflow.bun.test.ts
  • scripts/tests/pr-review-walkthrough-sanitize.bun.test.ts
  • scripts/tests/run_bun_tests.subprocess.test.ts
  • scripts/tests/test-file-coverage.bun.test.ts
  • scripts/tests/test-shard-orchestrator.test.ts
  • test-setup/augment-bun-vi.test.ts
  • test-setup/augment-bun-vi.ts
  • tsconfig.scripts.json
💤 Files with no reviewable changes (8)
  • scripts/bun-test-manifest-data-providers.ts
  • scripts/bun-test-manifest-validation.ts
  • scripts/bun-test-manifest-data-storage.ts
  • scripts/bun-test-manifest-data-mcp.ts
  • scripts/tests/bun-test-manifest.bun.test.ts
  • scripts/tests/ci-docs-only-skip.bun.test.ts
  • scripts/bun-test-manifest.ts
  • scripts/bun-test-manifest-data-tools.ts

Comment thread packages/agents/run-bun-tests.ts Outdated
Comment thread scripts/check-test-file-coverage.ts
Comment thread scripts/tests/bun-test-roots.bun.test.ts
Comment thread test-setup/augment-bun-vi.ts Outdated
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI N/A% N/A% N/A% N/A%
Core N/A% N/A% N/A% N/A%
CLI Package - Full Text Report
CLI full-text-summary.txt not found at: coverage_cli/packages/cli/coverage/full-text-summary.txt
Core Package - Full Text Report
Core full-text-summary.txt not found at: coverage_core/packages/core/coverage/full-text-summary.txt

For detailed HTML reports, please see the 'coverage-reports-24.x-ubuntu-latest' artifact from the main CI run.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Fixed in e110141. The inconsistency was real: discovery moved to import.meta.dir while the spawned child, its preload and the JUnit report still resolved against process.cwd(), so the two anchors agreed only when the runner was invoked from its own package directory.

Rather than passing absolute paths to the child, each of the three runners now anchors everything at one constant, which removes the class of bug rather than one instance of it:

const WORKSPACE_ROOT = import.meta.dir;
const PRELOAD = join(WORKSPACE_ROOT, 'bun-preload.ts');
const JUNIT_PATH = join(WORKSPACE_ROOT, 'junit.xml');
  • runTestFile spawns with cwd: WORKSPACE_ROOT instead of cwd: process.cwd() (all three runners).
  • PRELOAD is resolved against WORKSPACE_ROOT (core, auth). agents has no --preload flag; it relies on bunfig.toml, which the child now picks up from the correct cwd for the same reason.
  • The JUnit report is written to JUNIT_PATH (all three), so it lands in the workspace regardless of where the runner was started.
  • main() uses WORKSPACE_ROOT for discovery, and the per-file arguments stay relative — which is now correct, because they resolve against the child's cwd, which is that same root.

Verified: packages/auth 42/42, packages/core all files, packages/agents 338/340 (two rotating local flakes that also occur on main), and junit.xml written into each workspace directory rather than the invocation directory.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Fixed in e110141, and it mattered more than the symlink case alone — the guard's whole claim is exactly-once execution, so comparing lexical paths could both invent an uncovered file and hide a genuine duplicate.

Canonicalization had to be applied on both sides or the two sets would stop matching:

  • collectExecutorClaims now canonicalizes each discovered path before recording it in files/counts.
  • discoverRepositoryTestFiles canonicalizes its walk output the same way.

Both go through the injected BunTestRootDependencies.realpath rather than calling realpathSync directly, so the fixture-injected dependencies used by the tests still control the filesystem. findDoublyExecutedTestFiles gained the same deps parameter for this reason.

Added the symlink fixture you asked for — two executors scanning the same directory under different names — which asserts one real file yields exactly one duplicate entry claimed by both:

symlinkSync(join(getDir(), 'packages/a'), join(getDir(), 'packages/alias'));
// -> one duplicate, file === the real path, executors === both

The temp-fixture helper now returns canonical paths too: on macOS the temp root is itself reached through a symlink (/var -> /private/var), so a lexical fixture path would never compare equal. Guard still reports zero uncovered and zero doubly-executed on the real repository.

@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Both fixed in e110141.

Vacuous assertion — added expect(ordinary).toBeDefined(); before the timeout check, with a comment naming the failure mode, so renaming or removing run_bun_tests.test.ts fails the test instead of silently making the negative case pass on undefined.

Bounded drain docblock — corrected. The loop stops after MICROTASK_DRAIN_ROUNDS, so "arbitrarily deep" was wrong. It now reads:

Each await Promise.resolve() yields one microtask round; the loop drains async callback chains for up to MICROTASK_DRAIN_ROUNDS rounds, and the final queueMicrotask is the settling boundary (replacing the former setImmediate). The bound is deliberate: a callback that reschedules microtasks forever degrades to a bounded delay rather than an infinite hang, and a chain deeper than the bound stays pending by design.

The shared Bun test runner chose which files to execute from a
hand-maintained list in scripts/bun-test-manifest.ts. Any list that must
be edited by hand drifts, and this one had: 43 test files existed on
disk that no CI job ran, so they looked like coverage while asserting
nothing. Two of them had rotted into failures nobody could see.

Selection is now structural. scripts/bun-test-roots.ts declares, per
root, only how a file should run — cwd, scanned directories, filename
pattern, preloads, tsconfig, timeout, retries, globalSetup, credentials
and per-file timeout overrides. Which files run is answered by walking
the filesystem, so a new test file executes by virtue of existing. There
is no files/include/exclude member anywhere, and the walker now
propagates read, stat and realpath failures as BunTestRootStatError
rather than silently returning a short list: a dropped test is always
loud. Dot-prefixed directories are still pruned, but a dot-prefixed
file that matches the pattern is not, and files are deduplicated by
real path so a symlink alias cannot execute one file twice.

scripts/check-test-file-coverage.ts is what keeps this honest. It walks
the repository for test files and fails when one is executed by no
executor or by more than one, deriving the covered set from each
executor's own discovery code rather than restating it. It runs on every
non-docs PR from bun_test_orchestrator_smoke, so it cannot be skipped by
affected-shard selection on exactly the PR that would introduce a gap.
That subsumes the bun_native_test_parity job, which is deleted.

Consequences of running everything:

- providers gains 41 files, tools 1, storage 1. Three failed and are
  fixed: a stale vi.mock specifier, an assertion ordered after a Bun
  matcher that unfreezes its subject, and an ISO literal that did not
  match its own input timestamp.
- cli and core lose their shared roots and agents keeps only test-bun;
  their bespoke runners already discover those files, and agents/src was
  running twice per CI run.
- core and auth broaden to *.spec, adding 13 files they had been
  skipping, and their JUnit classnames strip the suffix accordingly.
- lsp moves onto the shared runner. Its bare `bun test` could only be
  modelled by the guard, never derived, which was a hole in the
  guarantee.

core, agents and auth gain import.meta.main guards and export the
discovery function main() itself calls, so the guard reads what CI runs.
Running every test file surfaced seven packages/providers auth suites the
deleted manifest had commented out with "Bun fake-timer incompatibility on
Linux CI ... re-add when Bun runtime is fixed". Every one of their cases
timed out at exactly the per-test timeout on Linux while passing on macOS.

Reproduced in oven/bun:1.3.14 on linux/arm64. The compat shim's
flushPendingTasks ended its microtask drain by awaiting a setImmediate.
Under Bun's fake timers, once a timer has fired and the clock is then
advanced with no pending timers, setImmediate (and setTimeout(_, 0)) is
gated by the fake-timer scheduler and never becomes due, so the await
never returns and advanceTimersByTimeAsync hangs until teardown. macOS
happens to keep firing it, which is why the failure looked platform
specific. Probes in that state show queueMicrotask, process.nextTick and
Promise.resolve all return in under a millisecond, because microtasks
drain inside the current macrotask before the scheduler regains control.

The settling boundary is now a microtask. That also restores Vitest
parity the macrotask had broken: for a timer whose awaited continuation
schedules a nested timer, Vitest fires it at 25ms and the shim's own test
asserted 35ms — the extra macrotask turn had deferred the continuation
past the next timer. Confirmed by running the same scenario under Vitest.

Also repoints two orchestrator tests at the surviving scripts roots; they
still asserted the scripts-tests-slow root that folded into a per-file
timeout override.
)

The field was described as a filename pattern while resolveTimeoutForFile
tests it against the resolved absolute path, so a basename-anchored regex
would silently never match and the file would quietly keep the root
timeout. Path matching is the behavior worth keeping — it lets an override
scope itself to a directory — so the contract is documented rather than
changed, in the interface, the root field, and dev-docs/bun.md.
…#2979)

Two soundness gaps found in review of the discovery work.

The core, agents and auth runners discovered test files under
import.meta.dir and then mapped them back to paths relative to it, while
the spawned child, its preload and the JUnit report still resolved
against process.cwd(). The two anchors agree only when the runner is
invoked from its own package directory, which the workspace scripts
happen to satisfy, so this was latent rather than broken. Each runner now
anchors everything at a single WORKSPACE_ROOT constant: discovery, the
child's working directory, the preload and the report path.

The coverage guard compared lexical paths. An executor that reaches a
test through a symlink alias would then be credited with a file the
repository walk recorded under a different path, reporting a false
uncovered file on one side and hiding a genuine duplicate on the other —
which would undermine the exactly-once guarantee the guard exists to
prove. Both the repository walk and the executor claims now canonicalize,
so one real file has one coverage identity, and a fixture with two
executors scanning the same directory through different names asserts it.
The temp-fixture helper canonicalizes too, since on macOS the temp root
is itself reached through /var -> /private/var.

Also asserts the timeout-override control file was actually found before
checking that it has no override, so the negative case cannot pass
vacuously, and corrects the shim's drain docblock: the loop is bounded at
MICROTASK_DRAIN_ROUNDS, so it does not drain arbitrarily deep chains.
AuthDialog's Close case wrote a keystroke, slept a fixed 50ms and then
asserted onSelect had fired. On a loaded CI runner that is not enough for
the render and keypress to propagate, and the cli shard failed on it twice
in a row while passing locally and on the two preceding CI runs of this
same branch.

The three sibling cases in this file were already converted to waitFor for
exactly this reason and carry the comment explaining it; this one was
missed. It now polls the same way, so the assertion is unchanged and only
the waiting strategy differs.
Deleting the shared cli root means packages/cli/run-bun-tests.ts is the
only executor for the workspace, and it discovers every file rather than
consulting a per-file configuration. A suite that drives the real
TodoStore against disk therefore cannot opt into storage isolation the
way the deleted manifest entry did for
src/ui/contexts/__tests__/todoProvider.observation.bun.tsx (#3052).

isolateStorageRoots() has to run before any test module imports the
Storage singleton, so it belongs in the workspace preload rather than in
a test. Every other workspace that touches storage already preloads it
this way. Without it that suite would read and write the developer's real
storage root.
@acoliver

acoliver commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Rebase reconciliation onto latest main

Rebased onto 92b8ff8d5. Two things reconciled, both worth calling out for review:

The canary fix is now main's, not mine. #3097 landed a fix for the same ocr-concurrency-canary-2673 failure while this PR was in flight. Its approach is strictly better than the one I had pushed: it defers response.destroy() until the client has actually received the partial body, which makes the reset deterministic and keeps the strict statusCode === 200 assertion, whereas mine relaxed the client-side assertion to accept either RST outcome. I dropped my commit and took main's version verbatim — this PR now contains no change to that file.

packages/cli gained a storage-isolation preload. #3052 landed a new shared-runner root for src/ui/contexts/__tests__/todoProvider.observation.bun.tsx declaring preload: ['test-setup-storage-isolation.ts', 'bun-test-setup.ts']. This PR deletes the shared cli root, so that file is now executed by packages/cli/run-bun-tests.ts, which discovers every file and has no per-file configuration to carry that preload. Without action the suite would have driven the real TodoStore against the developer's actual storage root.

isolateStorageRoots() has to run before any test module imports the Storage singleton, so it belongs in the workspace preload rather than in a test — which is exactly how every other storage-touching workspace already does it. packages/cli/bunfig.toml now preloads ./test-setup-storage-isolation.ts (the file already existed in the workspace; nothing was pointing at it). The suite passes under discovery, and the full local cli run went from 668/671 to 671/671 — the three src/integration-tests/* failures that had been failing locally all along now pass too.

Final state: 43/43 checks green, all review threads resolved, MERGEABLE/CLEAN on 3025f13a0.

acoliver added a commit that referenced this pull request Aug 7, 2026
 #3115) (#3119)

The nightly release job creates its GitHub Release with
`gh release create --target <sha>`, which asks the Releases API to
materialize the tag. Since GitHub's Nov-2023 "enforcing workflow scope
when creating a release" change that endpoint requires `workflows: write`
in addition to `contents: write` whenever the target commit's
`.github/workflows/` tree has drifted from the default branch tip --
and `workflows: write` cannot be granted to `GITHUB_TOKEN` through a
`permissions:` block. The refusal arrives as an opaque
`HTTP 403: Resource not accessible by integration`.

Because a release run takes about an hour, any PR touching `.github/`
that merges to main during the run creates exactly that drift. Run
31112396891 lost its tag and Release this way after PR #3103 landed
thirteen minutes in, leaving npm and ghcr published but the repository
untagged.

Create the tag through `POST /repos/{owner}/{repo}/git/refs` instead,
then create the Release from the now pre-existing tag with no `--target`.
That endpoint needs only `contents: write` and does not pass through
receive-pack, so the workflow-file guard never applies. A plain
`git push` of the tag is not an alternative: it goes through
receive-pack and is rejected the same way (actions/checkout#1421).

Dropping `--target` on its own would be worse than the bug, since the
Releases API would then default the tag to the default branch tip and
silently tag a commit other than the one that was built and published.

Tag handling is idempotent for resumed runs: an existing tag is peeled
if annotated and reused when it resolves to the release commit, and is
a hard error when it does not. The existence probe uses
`git/matching-refs`, which returns 200 with an empty array when nothing
matches, so any non-zero `gh` exit is a genuine failure that aborts the
step -- the previous shape would have masked it. Matching-refs is a
prefix search, so the result is filtered for exact ref equality; this
repository has real prefix collisions, with `v0.11.0` matching every
`v0.11.0-nightly.*` tag.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delete the Bun compatibility job and manifest allowlist; run every test by discovery

1 participant