Skip to content

Migrate remaining workspaces and finalize CI to Bun-native (Fixes #2847) - #3014

Merged
acoliver merged 33 commits into
mainfrom
issue2847
Aug 4, 2026
Merged

Migrate remaining workspaces and finalize CI to Bun-native (Fixes #2847)#3014
acoliver merged 33 commits into
mainfrom
issue2847

Conversation

@acoliver

@acoliver acoliver commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2847.

Migrates every test root named in #2847 to Bun's native test runner and moves CI's test execution onto it. agents and cli still run under Vitest and are tracked separately by #2578 — see "Remaining Vitest usage" below for the full, honest enumeration.

Test roots replace curated file lists

scripts/bun-test-manifest.ts previously listed every Bun-ready file by hand, because a partially migrated workspace could not distinguish a Bun-ready file from one still owned by Vitest. A root now selects its files in one of two ways:

  • include / exclude globs for fully migrated roots — the Bun-native equivalent of a Vitest config's include. This is what makes "no test file can be silently dropped" mechanically true: a newly added test file runs without a manifest edit.
  • files for roots still finishing their migration.

A root may also declare preload (one or more, the equivalent of Vitest setupFiles), tsconfig (a test-only --tsconfig-override), timeout, retries, globalSetup (setup()/teardown() run once in the runner process, so the env it mutates is inherited by every spawned test process), and credentialed.

credentialed marks a root that calls a real provider. An unfiltered run covers every other root — the complete offline suite — so the PR gate never burns quota; evals and integration-tests are requested by name from their own workflows.

What now runs under Bun

Root Files
settings 15
ide-integration 10
vscode-ide-companion 7
policy 12
telemetry 13
test-utils 11
a2a-server 21
scripts/tests 202
evals 1
integration-tests 31

Each migrated workspace's test / test:ci invokes scripts/run_bun_tests.ts and still emits junit.xml for the CI test reporter. scripts/test.ts, the root package.json scripts, .github/workflows/ci.yml, dev-docs/bun.md and CONTRIBUTING.md all point at the Bun-native path, with bun run test:bun as the single canonical command.

Integration tests need real provider credentials, so they cannot pass locally; what was verified is that all 31 files load and collect under Bun, with the residual failure being the same assertProviderConfig error Vitest reports. evals was verified end to end: the report lands at evals/logs/report.json and scripts/aggregate_evals.ts parses it.

Compatibility gaps closed in the shim

Each of these was a whole class of failures rather than a single file:

  • it / test / describe.runIf — Bun ships skipIf but not runIf, so gated tests failed to even collect.
  • automockValue now mirrors accessors instead of reading them. node:fs exposes getters backed by private class fields that throw off-instance, which aborted the automock of the whole module.
  • restoreAllMocks also resets spy state, matching Vitest's mockRestore. Without it a spy installed over an automocked export kept its call history across tests.
  • waitFor under Bun's fake timers: the loop advanced the clock but never yielded, so a promise chain resumed by a timer could not progress between attempts. It also attempts the callback at t=0 like the real-timer path, and no longer advances twice between retries after an async rejection.

PTY

@lydell/node-pty never delivers onData/onExit under Bun on POSIX (oven-sh/bun#25822), so the interactive harness selects Bun.spawn's terminal backend (packages/test-utils/src/pty-backend.ts). It is not shared with core's adapter because core already dev-depends on test-utils, and importing core here would close a dependency cycle.

Bugs the migration exposed

  • waitFor deadlock. The openai-responses abort suite hangs on main under Bun; it now passes.
  • Eval log directory was cwd-relative, reproducing Fix nightly eval configuration and result reporting #2605 under the runner's working directory. It is now resolved from the module.
  • JUnit conversion double-counted. Bun nests a describe suite inside the file-level suite; attributing the nested suite's cases to its parent doubled every count the evals aggregation reads.
  • token-tracking-property.test.ts's 24 property tests were inert. @fast-check/vitest v0.2 dropped the 3-argument itProp(name, [arbs], fn) form, so the arbitraries were swallowed as an options object and each predicate ran once with the Vitest context instead of generated values. Driving them through plain fast-check exposed three assertions that never held — total deliberately excludes cache tokens, formatSessionTokenUsage groups digits via toLocaleString(), and a freshly recorded entry is inside the 60-second window. Same test count (24 pass, 1 skip); now with real generated values.

Retired Vitest configuration

Ten vitest.config.ts files are deleted. The invariants they guarded are re-expressed against the manifest rather than dropped: the evals report path (scripts/tests/evals-report-path.test.ts), the OCR workflow's test discovery, the scripts shard's two invocations, and the settings boundary alias check.

Remaining Vitest usage

Vitest is no longer the runner for any root named in #2847, but it is not yet absent from the repository. Enumerated honestly:

Still executes Vitest (out of this issue's scope, tracked by #2578):

Path What runs it
packages/agents test / test:ci the agents shard, via scripts/test.ts
packages/cli test / test:ci (+ test:integration, test:ci:covered, test:ci:fast, test:legacy) the cli shard, via scripts/test.ts
packages/storage test:vitest the secure_store_backend job in ci.yml and its nightly twin, which need the two backend-specific configs
packages/test-utils/src/quota-guard-vitest-integration.test.ts spawns a nested Vitest deliberately — it is the test of Vitest integration

Does not executevitest stays in devDependencies because migrated test files still import describe/it/expect from the vitest specifier, which Bun resolves through its own injected handler. test:vitest escape hatches remain on auth, lsp, mcp, providers, storage and tools; no workflow or test script invokes them.

So the issue's "CI uses Bun-native execution as the primary path for all workspaces" holds for every root this PR owns, and does not yet hold for agents, cli or the SecureStore backend matrix.

Two decisions worth a second opinion

  1. Quota-guard semantics. Under Vitest a tripped provider-quota sentinel skipped fresh tests (keeping the run green) and threw on retries. Bun has no way to skip from inside a hook, so it now always throws: the API is still never called, but a quota outage turns e2e red rather than skipped.
  2. bun_native_test_parity cost. It now runs the complete 798-file non-credentialed manifest at a 90-minute cap, which substantially duplicates test_shard. Repurposing it as a manifest-completeness gate would give the same protection far more cheaply.

Verification

npm run typecheck (all workspaces), npx eslint, npx prettier --check, npm run lint:eslint-guard, npm run build, and the CLI smoke (bun scripts/start.ts --profile-load stepfun-37) all pass. The complete non-credentialed manifest and every migrated workspace suite pass locally.

Open Code Review found six issues, all remediated in this branch. No test was dropped, skipped or filtered; no lint rule, complexity threshold or type suppression was loosened — the runner test file was split rather than raising max-lines.

Summary by CodeRabbit

  • New Features

    • Added a unified Bun-based test runner supporting workspace selection, filters, retries, setup/teardown hooks, and test reporting.
    • Added JUnit-to-JSON report conversion for improved CI result visibility.
    • Added cross-platform support for interactive terminal tests.
  • Bug Fixes

    • Improved path handling, cleanup, timeout behavior, quota failures, and endpoint shutdown checks.
    • Improved compatibility across Bun and Vitest test environments.
  • Documentation

    • Updated testing guidance with the canonical full-suite command and separate evaluation test instructions.

acoliver added 13 commits August 3, 2026 13:30
Adds glob-based roots, multiple preloads, per-root tsconfig/timeout/retries
and globalSetup to the Bun-native test manifest and runner, and closes three
Vitest-compatibility gaps (it/test/describe.runIf, accessor-safe automock,
restoreAllMocks resetting spy state). Unblocks the settings and
ide-integration workspaces under Bun.
@lydell/node-pty never delivers onData/onExit under Bun on POSIX, so the
interactive test harness selects Bun.spawn's terminal backend there. Also
preserves the default export when the script harness patches node:fs.
…ation roots

Declares glob-based Bun-native roots for settings, ide-integration,
vscode-ide-companion, policy, telemetry, test-utils, a2a-server, the script
harness, evals and integration-tests, and points those workspaces' test
scripts at the Bun-native runner. Adds a Vitest-compatible JSON report so the
evals aggregation keeps working, and replaces the Vitest-internal
@fast-check/vitest dependency in the token-tracking property suite with plain
fast-check.
The 3-argument itProp form was dropped by @fast-check/vitest v0.2, so the
arbitraries were swallowed as an options object and every predicate ran once
with the Vitest context instead of generated values. Driving them through
plain fast-check exposed three assertions that never held: total deliberately
excludes cache tokens, formatSessionTokenUsage groups digits via
toLocaleString, and a freshly recorded entry is inside the 60-second window
rather than outside it.
…suite

Root scripts, scripts/test.ts and the CI workflows now invoke the Bun-native
runner for the script harness, evals and integration tests. Roots that call a
real provider are marked credentialed so an unfiltered run stays offline and
the parity job can execute the complete suite without burning quota.
packages/cli compiles pty-backend.ts through a tsconfig that does not list
Bun's types, so process.versions.bun and the Bun global are read through
explicit narrow shapes instead.
Deletes the ten now-unused vitest.config.ts files and re-expresses the
invariants that guarded them against the manifest: the evals report path, the
OCR workflow's discovery, the scripts shard's two invocations and the settings
boundary's alias check. Also fixes two bugs the migration exposed — the eval
log directory was cwd-relative (issue 2605 again under the new cwd) and the
JUnit converter attributed a nested suite's cases to its parent, doubling
every count the evals aggregation reads.
…-timer waitFor

The compat shim had grown try/catch fallbacks that turned an unresolvable
vi.mock target into a silently registered empty module, defeating the
behavioural test that guards that fail-fast contract. Reverted.

waitFor's fake-timer loop advanced the clock but never yielded before
retrying, so a promise chain resumed by a timer could not make progress
between attempts; the openai-responses abort suite deadlocked on main and
failed here. It now yields to the microtask queue between attempts.
…ing it

stop() resolving means the server relinquished the socket, but releasing the
listening descriptor is the runtime's job and is not necessarily complete on
the next turn. The assertion still requires a refused connection; it just
gives the runtime a bounded budget to get there.
…path

waitFor's fake-timer loop now attempts the callback at t=0 like the real-timer
path and no longer advances the clock twice between retries after an async
rejection. The process.exit shim interpolates the raw argument so a no-arg
call reports "undefined" exactly as Vitest does. Adds the missing
resource-safety test for a global setup that throws after an earlier one
succeeded.
…dget

The subprocess-integration blocks move to their own file rather than raising
the max-lines threshold.
@coderabbitai

coderabbitai Bot commented Aug 4, 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: 36 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: b1703c97-6e02-45a7-83c0-e52fc259c96b

📥 Commits

Reviewing files that changed from the base of the PR and between b1e1687 and 8030ca4.

📒 Files selected for processing (5)
  • scripts/bun-test-manifest-data-providers.ts
  • scripts/bun-test-manifest.ts
  • scripts/tests/bun-test-manifest.bun.test.ts
  • scripts/tests/run_bun_tests.subprocess.test.ts
  • tsconfig.scripts.json
📝 Walkthrough

Walkthrough

This pull request migrates test orchestration toward Bun-native execution. It adds manifest-driven discovery, asynchronous lifecycle handling, JUnit report conversion, PTY compatibility, workspace script updates, CI changes, and runner-portable test adjustments.

Changes

Bun test migration

Layer / File(s) Summary
Manifest and report infrastructure
scripts/bun-test-manifest.ts, scripts/bun-test-manifest-validation.ts, scripts/bun-junit-to-json-report.ts
Adds glob-based roots, exclusions, per-entry settings, validation, lifecycle metadata, and JUnit-to-Vitest JSON conversion.
Asynchronous Bun runner
scripts/run_bun_tests.ts, scripts/tests/run_bun_tests.*
Adds filters, retries, preloads, global setup and teardown, timeout handling, synthetic failures, and report reconciliation.
Orchestration and CI wiring
package.json, packages/*/package.json, scripts/test.ts, .github/workflows/ci.yml, CONTRIBUTING.md
Updates workspace commands, script sharding, CI manifest checks, evaluation reporting, and test documentation.
Runtime compatibility
test-setup/*, packages/test-utils/src/*, packages/vscode-ide-companion/*
Adds Bun-compatible mocks, fake-timer polling, PTY backends, VS Code stubs, and platform-specific test setup.
Migrated test suites
integration-tests/*, scripts/tests/*, packages/*/src/**/*.test.ts
Removes Vitest context dependencies and updates paths, cleanup, assertions, subprocess handling, and runner-specific behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The evidence supports the migration and CI objectives, but dev-docs/bun.md cannot be verified because it was excluded by the !dev-docs/** filter. Review dev-docs/bun.md, excluded by !dev-docs/**, to confirm it documents the canonical Bun command and remaining Vitest usage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the primary changes: migrating remaining workspaces and finalizing CI on Bun-native execution.
Description check ✅ Passed The description is detailed, includes testing results and the linked issue, and mostly covers the template despite omitting its explicit section headings.
Out of Scope Changes check ✅ Passed The changes support Bun migration, CI orchestration, compatibility, reporting, test portability, and migration-related test failures.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch issue2847
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue2847

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 4, 2026
…ntract

A review flagged the missing t=0 attempt by analogy with the real-timer path,
but stub-helpers.bun.test.ts asserts the opposite for Bun: nothing else moves
the fake clock, so advancing before the first callback is what lets a callback
waiting on a scheduled effect ever succeed. The double-advance fix after an
async rejection stands.
# Conflicts:
#	.github/workflows/ci.yml
#	scripts/bun-test-manifest.ts
#	scripts/tests/bun-test-manifest.bun.test.ts
#	scripts/tests/test-setup.ts
#	scripts/tests/vitest.config.ts
#	tsconfig.scripts.json
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Before this PR, the repository had already moved install and some orchestration onto Bun, but many workspaces and script-harness tests still relied on Vitest or were only partially covered by the native parity job. That left multiple test runners, fallback scripts, and CI paths in place, so contributors could still run or validate suites through older npm/Vitest flows and some test files could be omitted without immediate failure. After this PR, the remaining workspaces are migrated to Bun-native test execution, the root orchestrator and CI use Bun-native as the primary path, and the manifest/tests/configs are updated so the complete suite is exercised and silently dropped test files are no longer possible.

Release Notes

New Features

  • Canonical Bun-native test execution path for the remaining migrated workspaces via manifest-based bun test orchestration.
  • Include/exclude glob support in the Bun-native manifest, making silent test-file omissions mechanically fail.
  • CI now runs the complete Bun-native suite as the primary verification path instead of a sample or Vitest fallback.

Bug Fixes

Tests

  • Adds/updates manifest entries and behavioral tests covering the migrated workspaces and script harness.
  • Updates workspace and root test configs so Bun-native execution is the enforced default.
  • Expands coverage for Bun install/link behavior, native timer regressions, and orchestrator/manifest validation.

Documentation

  • Updates dev-docs/bun.md and dev-docs/test-runner-inventory.md to reflect Bun-native as canonical.
  • Updates contributor guidance in CONTRIBUTING.md to the single supported Bun-native test workflow.

Refactor

  • Migrates workspace test scripts from Vitest to native bun test where applicable.
  • Removes redundant Vitest runner configurations for migrated workspaces.
  • Simplifies CI test jobs to invoke Bun-native execution consistently.

Chore

  • Updates package.json, tsconfig.scripts.json, workspace package.json/vitest.config.ts files, and .github/workflows/ci.yml to complete the Bun-native migration.

Changes

Layer File(s) Summary
scripts scripts/test.ts, scripts/bun-test-manifest-data-providers.ts, scripts/check-settings-boundary.ts, scripts/bun-test-manifest-validation.ts, scripts/run_bun_tests.ts, scripts/bun-test-manifest.ts, scripts/bun-junit-to-json-report.ts Changes in scripts
. CONTRIBUTING.md, package.json, tsconfig.scripts.json Changes in .
project-plans/issue2847 project-plans/issue2847/pr-body.md, project-plans/issue2847/plan.md Changes in project-plans/issue2847
scripts/tests scripts/tests/run_bun_tests.global-setup.test.ts, scripts/tests/pr-review-walkthrough-sanitize.bun.test.ts, scripts/tests/ocr-telemetry.test.ts, scripts/tests/get-release-version.test.ts, scripts/tests/bun-junit-to-json-report.test.ts, scripts/tests/issue-planner-enrichment.bun.test.ts, scripts/tests/ocr-review-workflow.bun.test.ts, scripts/tests/ocr-concurrency-canary-2673-helpers.ts, scripts/tests/run_bun_tests.subprocess.test.ts, scripts/tests/issue-planner-confinement.bun.test.ts, scripts/tests/issue-2603-release-install.test.ts, scripts/tests/native-timer-regression.test.ts, scripts/tests/vitest.config.ts, scripts/tests/run_bun_tests.test.ts, scripts/tests/bun-test-manifest.bun.test.ts, scripts/tests/bun-manifest-root-ownership.bun.test.ts, scripts/tests/test-setup.ts, scripts/tests/test-orchestrator.test.ts, scripts/tests/evals-report-path.test.ts, scripts/tests/test-shard-orchestrator.test.ts, scripts/tests/vitest-coverage.test.ts, scripts/tests/evals-vitest-config.test.ts Changes in scripts/tests
packages/vscode-ide-companion/test-stubs packages/vscode-ide-companion/test-stubs/vscode.ts Changes in packages/vscode-ide-companion/test-stubs
integration-tests integration-tests/run_shell_command.windows.test.ts, integration-tests/web-search-provider.test.ts, integration-tests/utf-bom-encoding.test.ts, integration-tests/setup-quota-guard.ts, integration-tests/token-tracking-ui-behavioral.test.ts, integration-tests/setup-fast-check.ts, integration-tests/token-tracking-property.test.ts, integration-tests/session-summary.test.ts, integration-tests/vitest.config.ts, integration-tests/token-tracking.test.ts Changes in integration-tests
packages/vscode-ide-companion packages/vscode-ide-companion/tsconfig.bun-test.json, packages/vscode-ide-companion/vitest.config.ts, packages/vscode-ide-companion/package.json Changes in packages/vscode-ide-companion
packages/test-utils packages/test-utils/vitest.config.ts, packages/test-utils/package.json Changes in packages/test-utils
packages/vscode-ide-companion/src packages/vscode-ide-companion/src/ide-client-integration.test.ts Changes in packages/vscode-ide-companion/src
packages/policy packages/policy/package.json, packages/policy/vitest.config.ts Changes in packages/policy
packages/a2a-server packages/a2a-server/vitest.config.ts, packages/a2a-server/package.json Changes in packages/a2a-server
packages/cli packages/cli/package.json Changes in packages/cli
packages/settings packages/settings/vitest.config.ts, packages/settings/package.json Changes in packages/settings
evals evals/vitest.config.ts, evals/test-helper.ts Changes in evals
packages/ide-integration packages/ide-integration/vitest.config.ts, packages/ide-integration/package.json Changes in packages/ide-integration
dev-docs dev-docs/bun.md, dev-docs/test-runner-inventory.md Changes in dev-docs
packages/ide-integration/src/ide packages/ide-integration/src/ide/process-utils.test.ts Changes in packages/ide-integration/src/ide
packages/test-utils/src packages/test-utils/src/interactive-run.ts, packages/test-utils/src/pty-backend.ts, packages/test-utils/src/test-rig.ts, packages/test-utils/src/interactive-run.test.ts Changes in packages/test-utils/src
packages/telemetry packages/telemetry/package.json, packages/telemetry/vitest.config.ts Changes in packages/telemetry
packages/settings/src/profiles/tests packages/settings/src/profiles/tests/ProfileManager.test.ts Changes in packages/settings/src/profiles/tests
packages/ide-integration/src/lsp/tests packages/ide-integration/src/lsp/tests/lsp-entry-path.test.ts Changes in packages/ide-integration/src/lsp/tests
.github/workflows .github/workflows/ci.yml Changes in .github/workflows
test-setup test-setup/augment-bun-vi.ts, test-setup/stub-helpers.ts Changes in test-setup
packages/agents packages/agents/package.json Changes in packages/agents
bun-junit-KAaFo3 bun-junit-KAaFo3/0.xml Changes in bun-junit-KAaFo3

Sequence Diagram

sequenceDiagram
  participant CI as GitHub Actions
  participant Selector as shard_selector
  participant Shard as test_shard
  participant Orchestrator as scripts/test.ts
  participant Manifest as bun-test-manifest.ts
  participant Runner as run_bun_tests.ts
  participant Bun as Bun native test

  CI->>Selector: changed files, event type
  Selector->>Selector: resolve affected shards from import graph
  Selector-->>CI: matrix of shards, has_tests flag

  CI->>Shard: run for each selected shard
  Shard->>Shard: bun install, restore bun.lock
  Shard->>Shard: npm run build
  Shard->>Orchestrator: bun scripts/test.ts --shard <shard>

  Orchestrator->>Manifest: resolveBunNativeTestFiles(repoRoot, workspace?)
  Manifest-->>Orchestrator: list of BunTestFile entries with cwd/preload

  loop for each manifest file
    Orchestrator->>Runner: runSingleTestFile(entry, baseArgs, deps)
    Runner->>Bun: Bun.spawnSync([bun, test, --preload?, file])
    Bun-->>Runner: exitCode, stdout, stderr
    Runner->>Runner: isChildSuccess(child)
  end

  Runner-->>Orchestrator: passed/failed counts
  Orchestrator-->>Shard: exit code
  Shard->>Shard: write junit.xml, upload coverage if applicable
  Shard-->>CI: job result
Loading

Magnitude

🎯 5 (XXL)
5031 additions, 2367 deletions, 78 changed files across 9 packages, 20 acceptance criteria

Related

Pre-merge Checks

Check Status Note
Title Clear and descriptive: identifies the migration to Bun-native test execution, mentions finalizing CI, and references the fixed issue #2847.
Description Comprehensive body covering all expected areas: migration summary, technical details, compatibility fixes, PTY handling, bug fixes, retired configs, remaining Vitest usage, verification steps, and linked issues. Template sections are present as substantive content, though not always with explicit matching headers.
Linked Issues Actual changes fulfill #2847's acceptance criteria: all 202 test files across small workspaces, scripts/tests, evals, and integration-tests are migrated; CI and root orchestration updated; documentation reflects canonical Bun-native command; remaining Vitest usage is honestly enumerated and proven unrelated to the migrated suite.
Out of Scope #2578 remains open for full agents/cli migration (cli currently chains Vitest + Bun-native per the PR's documented interim state). Two decisions are flagged for reviewer input: quota-guard semantics shift from skip-to-throw, and repurposing bun_native_test_parity as a manifest-completeness gate. packages/agents and packages/storage/secure_store_backend retain Vitest for explicit, documented repository requirements outside this issue's scope.

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

@github-actions

github-actions Bot commented Aug 4, 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

…ndbox

The e2e workflow passes --exclude through to the integration-test command to
skip individual specs per sandbox mode; the Bun-native runner rejected it as
an unknown option. Both the '--exclude glob' and '--exclude=glob' spellings
are accepted, matching what Vitest took.
Both vi.mock targets name modules that no longer exist, so the mocks were
inert; Vitest tolerated the unresolvable ids, Bun does not. Neither test used
what the mock supplied. The PNG fixture resolved its asset from the process
cwd, which is integration-tests/ under the runner rather than the repo root.
The e2e workflow's second invocation narrows the run to one spec and a set of
test names, both Vitest CLI features the Bun-native runner rejected. Bare path
arguments now filter the resolved files by substring, as Vitest did, and the
name pattern is forwarded to Bun as --test-name-pattern.
@github-actions

github-actions Bot commented Aug 4, 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.

…honestly

The parity job now covers the script harness, several of whose tests execute
the built CLI; they previously ran only in the built scripts shard, so the job
needs the same build step.

The LSP case asserts what import.meta.resolve reports when the package is
installed, but only checked that the API exists. An install that never links
the workspace has the API and not the package, so the precondition now covers
both halves.
@acoliver

acoliver commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Merging main pushed bun-test-manifest.ts past the 800-line budget. The
providers entry moves out following the pattern main established for tools,
mcp and storage, rather than raising the threshold. File selection is
unchanged: 971 resolved files, no duplicates.
@acoliver
acoliver merged commit fd4c82f into main Aug 4, 2026
44 checks passed
acoliver added a commit that referenced this pull request Aug 6, 2026
The second branch dispatch (run 31039119968) turned E2E Windows, the CLI
bundle and every other leg green, and narrowed the rest to two things.

windows_ci: only getPty remained. The previous attempt was aimed at the wrong
runner. packages/core runs exclusively under Bun, and getPty short-circuits to
the Bun.Terminal adapter on Bun POSIX, so these three cases only ever EXECUTE
on Windows — the one platform where substituting the backend in the module
registry silently fails, because Bun's runner cannot reset the registry and
evaluates a module-mock factory once, eagerly. That is why they passed
everywhere a developer would look and failed only in CI. getPty now takes
injectable backend loaders, so the selection logic is driven the same way on
every runtime and the cases run locally under Vitest too.

macos_ci: with the canary race fixed, the job reached a step that failure had
been masking. "Run scripts-assignment Bun manifest" invokes a workspace that
 #3014 deleted from the Bun test manifest while leaving the step behind, so it
could never pass. Both of its test files are already covered by the
scripts-tests glob root and ran green in the same job's harness step, so the
dead step goes rather than the workspace coming back.

From review:

- scripts/bun-build.config.ts rendered a diagnostic's source position only for
  instanceof Error values, but Bun attaches BuildMessage/ResolveMessage
  objects, which are not Errors — so the file/line/column was always thrown
  away. Confirmed by probing Bun 1.3.14 directly.
- The CLI bundle test's artifact check was an opaque existsSync assertion even
  though the build can exit 0 having built nothing, and its process renderer
  printed the literal "null" for the streams spawnSync nulls out on a spawn
  failure — both precisely when the diagnostic matters most.
- The nested-vitest launcher accepted node.cmd, which spawnSync cannot execute
  without shell:true; on a Windows box where a .cmd shim shadows node.exe the
  child would have failed to launch at all.
- The credential-proxy shutdown comment claimed a Windows named-pipe
  half-close that libuv does not implement. What actually fixes it is
  ordering: server.close() waits for the connection count to reach zero, so
  stop() can no longer return before the peer observes the close. The socket is
  also paused before ending, since end() leaves the read side open and an
  in-flight frame would otherwise still mutate the token store mid-shutdown,
  and endAndDestroyAfter now guards writableEnded against a double end().
- The canonical start-time platform list is exported from the credential lock
  and imported by its tests, so the mirror cannot drift.
- Windows gained the complement of the is_background gate: it now asserts the
  rejection rather than merely skipping the case.

Fixes #3061
acoliver added a commit that referenced this pull request Aug 7, 2026
…3084)

* Build the CLI bundle through the real publish path in CI

The nightly cli_bundle_launch job has never passed. It called Bun.build
in-process from bun test, where the bundler does not rewrite ./src/cli.js
to src/cli.tsx, so it could not resolve the CLI entry own import. The same
build run as the prepack subprocess resolves it, so build the way npm pack
actually builds.

Spawn the Bun running the test rather than node_modules/bun/bin/bun: npm
11.16 no longer runs install scripts, so the bun postinstall never
materialises that binary in CI.

Render the diagnostics Bun attaches to a failed build. Bun.build rejects
with an AggregateError whose own message is the constant "Bundle failed";
reporting only that left the nightly failure undiagnosable.

Refs #3061

* Make the nightly platform legs pass on their own terms

The nightly has failed every night since 2026-08-01 across four independent
jobs. This addresses the remaining three.

windows_ci carried six portability defects, all of which asserted POSIX
behaviour on a platform where the production code deliberately behaves
differently:

- the credential-write-lock suite shelled out to `ps -o lstart=` for every
  platform, though production gates that probe to darwin/linux/freebsd and
  returns null elsewhere. It now mirrors the gate and fabricates an
  approximate owner record where no canonical source exists, so the test
  still runs on Windows rather than being skipped. Its M4 sabotage also
  moved to replacing the lock file with a directory: the old "remove the dir,
  write a file" trick raises ENOTDIR on POSIX but ENOENT on Windows, where
  readOwner reads it as "absent" and logs nothing, silently hiding the
  release failure the test exists to catch.
- toolRegistryFactory compared a path built from os.tmpdir() (an 8.3 short
  form on the runner) against the tool's resolved long path; the workspace
  root is now realpath'd so both sides speak the same path.
- getPty's vi.doMock was bypassed on Windows, where node-pty is a real
  installed native addon, so the assertions received the genuine module.
  File-level vi.mock intercepts at module-graph build time instead.
- process-run asserted an ENOENT message; Bun on Windows says "Executable not
  found in $PATH" for the same condition.
- the quota-guard integration tests spawned their nested vitest run with
  process.execPath, which is Bun here. Vitest is a Node tool whose forks pool
  assumes a Node runtime, so the child died on Windows and the run published
  no sentinel. The child is now pinned to Node, and the assertions report the
  child's own output so a nested run that dies for an unrelated reason is no
  longer indistinguishable from one that failed for the intended reason.

Dispatching the nightly on this branch also surfaced a regression that landed
after the failing run: the shell timeout-ceiling suite builds a background
invocation unconditionally, but ShellTool rejects is_background on win32
outright, so build() threw before the clamp behaviour was reached.

credential-proxy-server.stop() destroyed each connection abruptly. A Windows
named-pipe peer does not reliably observe that as a close, so the client kept
believing it was connected and its next request hung to the timeout instead of
reporting the loss. It now half-closes with the existing endAndDestroyAfter
helper, which sends EOF before force-destroying.

macos_ci failed on a race in the OCR canary: the monitor forwarded the
upstream 200 correctly (every telemetry assertion passed), but the client's
response callback and the request-level reset competed for one first-wins
resolve. The status is now captured when the response object is delivered and
a later reset cannot overwrite it.

e2e_full (windows) failed because the rig workspace lives under
.integration-tests/, which the repo root .gitignore excludes, so file
discovery resolved the enclosing repo and hid every workspace file from the
agent — "Listed 0 item(s). (5 ignored)". The model then refused to run a
script it could not see. The workspace is now its own git root.

Fixes #3061

* Fix the last two nightly legs and act on review

The second branch dispatch (run 31039119968) turned E2E Windows, the CLI
bundle and every other leg green, and narrowed the rest to two things.

windows_ci: only getPty remained. The previous attempt was aimed at the wrong
runner. packages/core runs exclusively under Bun, and getPty short-circuits to
the Bun.Terminal adapter on Bun POSIX, so these three cases only ever EXECUTE
on Windows — the one platform where substituting the backend in the module
registry silently fails, because Bun's runner cannot reset the registry and
evaluates a module-mock factory once, eagerly. That is why they passed
everywhere a developer would look and failed only in CI. getPty now takes
injectable backend loaders, so the selection logic is driven the same way on
every runtime and the cases run locally under Vitest too.

macos_ci: with the canary race fixed, the job reached a step that failure had
been masking. "Run scripts-assignment Bun manifest" invokes a workspace that
 #3014 deleted from the Bun test manifest while leaving the step behind, so it
could never pass. Both of its test files are already covered by the
scripts-tests glob root and ran green in the same job's harness step, so the
dead step goes rather than the workspace coming back.

From review:

- scripts/bun-build.config.ts rendered a diagnostic's source position only for
  instanceof Error values, but Bun attaches BuildMessage/ResolveMessage
  objects, which are not Errors — so the file/line/column was always thrown
  away. Confirmed by probing Bun 1.3.14 directly.
- The CLI bundle test's artifact check was an opaque existsSync assertion even
  though the build can exit 0 having built nothing, and its process renderer
  printed the literal "null" for the streams spawnSync nulls out on a spawn
  failure — both precisely when the diagnostic matters most.
- The nested-vitest launcher accepted node.cmd, which spawnSync cannot execute
  without shell:true; on a Windows box where a .cmd shim shadows node.exe the
  child would have failed to launch at all.
- The credential-proxy shutdown comment claimed a Windows named-pipe
  half-close that libuv does not implement. What actually fixes it is
  ordering: server.close() waits for the connection count to reach zero, so
  stop() can no longer return before the peer observes the close. The socket is
  also paused before ending, since end() leaves the read side open and an
  in-flight frame would otherwise still mutate the token store mid-shutdown,
  and endAndDestroyAfter now guards writableEnded against a double end().
- The canonical start-time platform list is exported from the credential lock
  and imported by its tests, so the mirror cannot drift.
- Windows gained the complement of the is_background gate: it now asserts the
  rejection rather than merely skipping the case.

Fixes #3061

* Reconcile the nightly fixes with a moving main

main moved substantially during the nightly repair and two of this branch's
changes were affected directly:

- #3078 implemented real Windows background shell jobs via Start-Process and
  removed ShellTool's win32 rejection. The timeout-clamp test therefore no
  longer needs a Windows gate (or the complementary rejection assertion added
  from review); it runs on every platform again.
- #3080 independently fixed half of the OCR canary race by retaining the status
  observed at the response callback. Taking main's version is correct, but it
  is not sufficient: reverting this branch's 50ms upstream-crash delay on top
  of it still failed 4 of 8 runs because the reset can preempt the HTTP parser
  before any response object exists. Keeping the delay passed 10 of 10.

The merge also confirms #3080's CLI asset-staging work composes with the
publish-path bundle test and build diagnostics here.

Address all three open review threads:

- prefer Bun's explicit diagnostic.message when present instead of relying on
  a custom diagnostic object's toString(), retaining String() as fallback;
- surface git-init status/stderr in the Windows E2E instead of silencing the
  command that establishes the workspace boundary;
- strip syntactic quotes from Windows PATH entries before looking for node.exe.

Refs #3061

* Close the remaining platform races exposed by nightly

The merged-head nightly turned the last failures into deterministic evidence:

- Windows PowerShell 5.1 parsed the BOM-less UTF-8 script as CP932, corrupting
  its Japanese filename literal before the shell command even ran. Give the
  script a UTF-8 BOM.
- Config's missing client-factory contract was checked only after parser and
  registry startup, allowing a simple dependency error to exceed the Windows
  test budget. Validate the required factory before initialization side effects.
- Windows log-cap enforcement killed the child before recording why. The exit
  event could therefore finalize the job first, as failed but without a reason.
  Claim a capping phase around taskkill so cap enforcement owns that transition.
- Graceful proxy shutdown waited for the three-second force timer on every
  cooperative connection because a peer can keep its write half open after our
  FIN. Destroy immediately after the FIN is flushed and retain the timer solely
  as a fallback.

These fixes preserve the behavioral assertions rather than widening timeouts or
weakening platform coverage.

Refs #3061

* Let same-process clients observe proxy shutdown before return

Windows named-pipe close notification can arrive one event-loop turn after the
server's close callback. Yield that turn before stop resolves so a client cannot
start a request using stale connected state and wait for the request timeout.

Refs #3061

* Match Windows filesystem behavior in the CLI

Windows surfaced two more Unix assumptions in the full nightly suite:

- realpath reports ENOENT when an existing parent component is a file. Walk the
  parent chain so the trust dialog still distinguishes a malformed path from a
  genuinely missing folder.
- fchmod is not supported for these Windows file handles. The capability
  transport is a Docker/Podman path whose Windows host permissions are not Unix
  modes, so retain the restrictive create modes and explicit fchmod enforcement
  on POSIX only.

Refs #3061

* Keep capability-directory validation on Windows (Refs #3061)

* Stabilize constrained cross-platform nightly runners (Refs #3061)

* Release bounded taskkill handles after Windows cleanup (Refs #3061)

* Guarantee bounded cleanup progress on Windows (Refs #3061)

* Make Windows process tests complete deterministically (Refs #3061)

* Serialize Windows core test processes (Refs #3061)

* Use Bun process completion for Windows shell jobs (Refs #3061)

* Observe Windows shell completion before releasing handles (Refs #3061)

* Wait for Windows log handles during test teardown (Refs #3061)

* Own Windows shell processes directly (Refs #3061)

* Stabilize Windows lifecycle assertions under load (Refs #3061)

* Keep Windows nightly assertions deterministic (Refs #3061)

* Reject invalid sandbox mount modes (Refs #3061)

* Make Windows shell-job cleanup deterministic (Refs #3061)

* Make cross-platform Nightly process lifecycle deterministic (Fixes #3061)

The Nightly suite failed on Windows and macOS for reasons that were real
lifecycle races rather than slow tests, so raising timeouts would only have
hidden them.

Windows shell jobs now terminate their whole process tree with bounded
taskkill handling, and cancellation, cap enforcement and disposal agree on
who owns a job's terminal transition instead of racing for it. Detached
background spawning uses PowerShell job-object semantics so the spawner is
not kept alive by its own child.

The core Bun runner distinguishes a child that has exited from one whose
pipes are still held by a descendant, reaps the POSIX process group rather
than only the direct child, and reports the effective per-file timeout so a
JUnit failure names the budget that actually applied.

Provider model listing is bounded by an abort signal instead of hanging on a
live fetch, the secure-store lock test observes acquisition deterministically,
and the VS Code companion proves shutdown by rebinding the released port and
reports a descriptive error when that deadline expires.

Tests converted for this work run on Bun and bun:test.

* Accept a read-only history in the turn error report context (Fixes #3061)

Two changes landed on main independently: chat history became a read-only
view, while the error-report context helper still required a mutable array.
The helper only slices the history, so widening the parameter restores the
agents declaration build without weakening the read-only guarantee.

* Check Windows PID liveness without spawning tasklist (Fixes #3061)

The Windows shell-job tests probed process liveness by spawning `tasklist`
with a 5s timeout, inside loops that polled every 200ms. `tasklist`
enumerates every process on the machine, so on a loaded CI runner it
exceeded that timeout, the probe threw ETIMEDOUT, and tests whose subject
had behaved correctly were reported as failures. The repeated spawns were
also the slowest part of each poll, making the timeout more likely the
longer a suite ran.

Signal 0 asks the kernel the same question directly: it succeeds for a live
pid, reports ESRCH for one that is gone, and EPERM for one owned by another
security context. Any other errno is a real fault and is rethrown rather
than reported as liveness. This is the approach the Windows spawn test
already used.

The probe existed in four near-identical copies, each with slightly
different error handling; they now share the single implementation in the
test-cleanup helper.

Also widens a session-listing timestamp assertion. It allowed 1ms of clock
skew between two reads, but the default Windows system timer ticks about
every 15.6ms, so the two could legitimately disagree by nearly a full tick.

* Remove POSIX assumptions from six Windows CI tests (Fixes #3061)

These are the last failures in the Nightly Windows job. Each asserted
something that is true only on POSIX, so each failed on Windows while the
behaviour under test was correct.

Trust rules are keyed by the resolved working directory, but the dialog test
expected a POSIX literal; path.resolve('/test/dir') is drive-qualified on
Windows. findEnvFile likewise resolves before walking, so the settings mock,
which matched on an unresolved join, never matched the path production
looked for.

Keyfile permissions and the colon-sanitizing command loader assert behaviour
Windows cannot express: NTFS reports 666 whatever the file was created with
and chmod there only toggles the read-only flag, and ':' in a filename is
reserved for alternate data streams, so such a file cannot exist for the
loader to find. Both are now stated as the POSIX contracts they are, with the
permission check split into its own test rather than an inline conditional.

The security test compared a raw path against JSON-encoded content, which
only matches when the path has no backslashes to escape; it now asserts on
the parsed settings. The sandbox test used the host resolver as a stand-in
for resolution inside the container, which is always Linux; on a Windows host
that rewrites the container destination, so it is POSIX-only.

* Treat an already-exited Windows process tree as reaped (Fixes #3061)

When a per-file timeout fires, the tree often exits between the timer and
taskkill running, and taskkill then reports a nonzero code because the pid no
longer exists. The runner took that as a reap failure and aborted the whole
Windows run, so one Bun process hanging at teardown ended the suite even
though every test in the file had passed.

That is the Windows analogue of POSIX ESRCH, which this code already ignores
for exactly the same reason. Rather than trust the exit code either way, the
reap now verifies the invariant it exists to establish — that nothing is left
alive holding the child's pipes — by waiting for close, and reports the
taskkill code only if the tree genuinely outlives the reap.

The behavioral test asserted the old contract, so it asserted the bug; it now
pins the corrected one.
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.

Migrate remaining small workspaces and finalize CI to Bun-native (#2578)

1 participant