Skip to content

Delete every Vitest escape hatch and guard against their return (Fixes #2970) - #3145

Merged
acoliver merged 13 commits into
mainfrom
issue2970
Aug 8, 2026
Merged

Delete every Vitest escape hatch and guard against their return (Fixes #2970)#3145
acoliver merged 13 commits into
mainfrom
issue2970

Conversation

@acoliver

@acoliver acoliver commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

TLDR

Removes Vitest from the repository and adds a CI guard so it cannot come back.

The headline fact is that Vitest could not run a single test here. Since #2969 moved every suite to bun:test, invoking it produces:

$ npx vitest run --config vitest.config.ts src/llm-types/modelEnvelope.test.ts
  import { describe, expect, it } from 'bun:test';
  Caused by: Error: Failed to load url bun:test (resolved id: bun:test)
  Test Files  1 failed (1) | Tests  no tests

So the six test:vitest scripts, ten config files, four Stryker configs and the coverage plumbing were not a fallback — they were a second test runner the project had to keep working, that had already stopped working. This deletes them, along with 28 dependency entries across every manifest and both lockfiles.

The one thing reviewers should look at: the ESLint change is a tightening, not the loosening it looks like. Five rules move to off, but @vitest/eslint-plugin identifies test blocks by import source and does not recognise bun:test, so all 17 of its rules had been silently inert since #2969. Effective enforcement goes from 0 rules to 7, which required fixing 28 genuine violations. Details and evidence below.

Fixes #2970. Terminal sub-issue of #2578 — when this merges, Vitest is gone.

Dive Deeper

Nothing here loses a working capability, because none of it worked. The issue's hard requirement is zero Vitest, with any genuinely irreplaceable capability treated as a blocker to resolve rather than a reason to keep a parallel runner. Each candidate was checked rather than assumed:

  • Stryker mutation testing — all four configs set testRunner: "vitest", three also pin configFile: "vitest.config.ts". git grep test:mutation .github/ returns nothing, so it was wired into no workflow, and dev-docs/stryker.md documented packages/cli/vitest.config.mutation.ts, deleted by an earlier slice. Removed; re-establishing it on Bun is a separate concern.
  • Coverage gating@vitest/coverage-v8 was the only producer, and no script anywhere passes --coverage, so packages/*/coverage was never written. CI's own comments already said cli and core "no longer upload a Vitest coverage artifact", and both downloads were continue-on-error reporting "N/A". The upload step and post_coverage_comment were moving nothing.
  • SecureStore keyring/fallback split — genuinely CI-gated, and the only item with real value. It moves to the Bun-native test:secure-store:keyring / test:secure-store:fallback scripts that already existed in packages/storage, emitting the same junit.secure-store.xml the reporter step consumes. The matrix axis changes from test-config to test-script.

The ESLint swap, with evidence. @vitest/eslint-plugin is replaced by eslint-plugin-jest with settings.jest.globalPackage: 'bun:test'. I proved the old plugin was inert by holding the file body and rules constant and changing only the import:

from 'vitest'    -> 3 errors (require-top-level-describe, prefer-strict-equal, valid-title)
from 'bun:test'  -> 0 errors, exit 0

Seven rules now run at error: expect-expect, no-conditional-expect, no-identical-title, valid-describe-callback, valid-title, require-to-throw-message, and max-nested-describe. Getting there meant fixing 28 real violations across 16 files. vitest/no-import-node-test becomes a no-restricted-imports entry banning node:test. Two rules encode Vitest-runner-only concepts and are genuinely inapplicable.

Five rules stay off with their options preserved so re-enabling is a one-word change. The reason is that eslint-plugin-jest implements them more strictly than @vitest/eslint-plugin did — measured directly: on the tree immediately before #2969 (a805a219f), where every test still imported vitest, the vitest plugin was live at error and lint was green at zero, the jest plugin reported 728 violations in packages/core alone. Burning down the ~4,183 repo-wide is tracked in #3129, which flags valid-expect as highest value since an un-awaited async assertion silently passes.

The guard. scripts/check-no-vitest.ts fails on an import (including a multi-line dynamic one), a dependency entry (including an npm:vitest@… alias, which has no forbidden key), a config file (vitest.config.*, vitest.workspace.*, or a vite.config.* carrying a test block), a lockfile entry, or a binary invocation in a manifest, workflow YAML, shell script, Makefile or TOML — while ignoring the word in prose or a comment.

Most of that breadth exists because an adversarial audit broke the first version six ways in ten minutes, including the two most likely real-world paths: a ./node_modules/.bin/vitest package script, and npx vitest run in a workflow YAML — which is exactly where this PR is deleting Vitest invocations from.

Three things the issue did not list but that break without a fix: packages/providers/src/package-boundary.test.ts asserted vitest.config.ts exists (inverted to assert absence, so it still fails loudly if Vitest returns); scripts/affected-lint-targets.ts and affected-test-shards.ts listed vitest.coverage.ts as a shard trigger; and tsconfig.scripts.json is an explicit allowlist, so the new guard files had to be added there or they would not be typechecked at all.

fsevents is removed from the install-script allowlist in scripts/tests/bun-workspaces.test.ts for the opposite reason to everything else here: it entered the tree only as an optional dependency of vite/rollup, pulled in by vitest. With Vitest gone nothing depends on it, and that guard correctly flagged the stale entry.

Reviewer Test Plan

npm run lint            # 0 errors, 0 warnings
npm run lint:no-vitest  # the new guard: 7,383 files, 0 violations
npm run typecheck       # all three chained projects
npm run build
npm run test:scripts    # note: `npm run test` is --workspaces and does NOT cover scripts/tests
npm run test

Worth exercising directly:

  • Try to smuggle Vitest back in. Each of these should fail npm run lint:no-vitest, and I would genuinely like to know if you find a seventh way:

    echo '{"scripts":{"test":"./node_modules/.bin/vitest run"}}' > packages/policy/package.json  # path-qualified binary
    printf 'jobs:\n  t:\n    steps:\n      - run: npx vitest run\n' > .github/workflows/x.yml    # workflow YAML
    echo 'export default [];' > vitest.workspace.ts                                              # official workspace file
    # also: npm: alias dependency, vite.config.ts with a test block, bare vitest in a Makefile

    Then confirm it does not fire on a comment containing "vitest run", or on a file named vitest.config.md.

  • Check the ESLint claim yourself. Take any test file, flip its import from bun:test to vitest, and watch rules that were silent start firing under the old plugin. That is the whole basis for calling this a tightening.

  • Confirm no test was weakened. The 28 lint fixes are the riskiest part, particularly the 19 no-conditional-expect restructurings, where turning a conditional assertion unconditional can quietly change what is asserted. They are all either logically equivalent or strictly stronger — e.g. bare toThrow() became toThrow('Cache-anchor seq must be a positive integer: got 0'), and four assertion-free await calls in provider-settings-integration.test.ts became real assertions.

Testing Matrix

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

Verified on macOS: format, lint, eslint-guard, no-vitest guard, typecheck, build, test:scripts, the workspace suite, and the CLI smoke test. Linux and Windows are left to CI.

Known flaky and unrelated: under heavy parallel load the packages/agents API specs and one test-utils file time out — a different file each run, all passing in isolation (agents workspace alone exits 0; test-utils 12/12). None are touched by this PR. This matches the load-dependent flakiness #3122 documented for the agents workspace.

Linked issues / bugs

Fixes #2970

Terminal sub-issue of #2578. Depends on #2969, merged as 18108c62c.
Follow-up: #3129 (enable the five deferred eslint-plugin-jest rules).

Summary by CodeRabbit

  • New Features

    • Added a repository-wide check that detects remaining legacy test-runner references and reports their locations.
    • Added comprehensive validation for the new test-runner migration.
  • Documentation

    • Updated contributor, evaluation, hook-writing, and planning guidance to use Bun-native testing.
  • Chores

    • Standardized test execution and type support across packages.
    • Removed obsolete test-runner configurations, coverage reporting, mutation testing, and CI coverage comments.
    • Updated CI and nightly workflows to use Bun-based testing.

…#2970)

Vitest could not run a single test in this repository. Since #2969 moved
every suite to bun:test, invoking it produced "Failed to load url bun:test"
and zero tests. The six test:vitest scripts, ten config files, four Stryker
configs and the coverage plumbing were all dead weight that still had to be
kept working. This removes them.

Nothing here loses a working capability, because none of it worked:

  - Stryker's four configs all set testRunner "vitest" and were wired into
    no workflow; dev-docs/stryker.md documented a config deleted earlier.
  - No test script anywhere passes --coverage, so packages/*/coverage was
    never produced and post_coverage_comment downloaded nothing.
  - The SecureStore keyring/fallback split moves to the Bun-native scripts
    that already existed in packages/storage, emitting the same junit file.

The ESLint swap is a tightening, not a loosening. @vitest/eslint-plugin
identifies test blocks by import source and does not recognise bun:test, so
all 17 of its rules had been silently inert since #2969. eslint-plugin-jest
with globalPackage bun:test restores real enforcement: seven rules now run at
error, which took fixing 28 genuine violations across 16 files. Five rules
stay off because jest implements them more strictly than vitest ever did --
on the pre-#2969 tree, where the vitest plugin was live and lint was green at
zero, the jest plugin found 728 violations in packages/core alone. That
burn-down is #3129.

The new lint:no-vitest guard fails on an import, a dependency (including an
npm: alias), a config file, a lockfile entry, or a binary invocation in a
manifest, workflow, shell script, Makefile or TOML -- while ignoring the word
in prose.
…uard

Open Code Review and an adversarial audit surfaced three classes of problem.

Collateral damage. Replacing the vitest ESLint block also deleted two
unrelated sibling blocks that ban self-imports in packages/core and
packages/cli. Restored verbatim. A stale fsevents entry in the install-script
allowlist is removed for the opposite reason: it entered the tree only via
vite/rollup under vitest, so with Vitest gone the guard was right to flag it.

Guard gaps. The guard missed a bare vitest command in a Makefile or workflow
YAML, and false-positived on a comment containing 'vitest run'. It also
reported every manifest violation as line 1 despite promising file:line:match.
The test helper built a bash -c string from an environment-derived path and
swallowed spawn errors whenever the exit code was non-zero, so a broken
harness was indistinguishable from a real failure; it now passes arguments
directly with no shell and throws on spawn failure or signal.

Diagnostics. Several assertions rewritten for jest/no-conditional-expect had
collapsed into expect(<boolean>).toBe(true), which prints only 'expected false
to be true'. They now use toMatchObject or narrow-then-assert, so a failure
shows the actual value.

Also drops the misleading jest version: '29' setting -- the runner is bun:test,
which has no Jest semver for the plugin to gate on.
@coderabbitai

coderabbitai Bot commented Aug 8, 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: 3 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: 50a825f5-ca93-4b14-b0e5-e6161b626bc8

📥 Commits

Reviewing files that changed from the base of the PR and between e2f0e22 and a4f6067.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock, !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • CONTRIBUTING.md
  • eslint.config.js
  • package.json
  • packages/cli/package.json
  • packages/providers/src/__tests__/LoadBalancingProvider.sessionPrecedence.test.ts
  • packages/providers/src/runtime/__tests__/profileApplication.lb.contextWindowTimeout.test.ts
  • scripts/tests/bun-workspaces.test.ts
  • scripts/tests/issue-2978-launcher-exec-bit.test.ts
📝 Walkthrough

Walkthrough

Changes

Bun test-runner migration

Layer / File(s) Summary
No-Vitest guard and validation
scripts/check-no-vitest.ts, scripts/tests/no-vitest-guard*, .github/workflows/ci.yml
Adds a repository-wide Vitest scanner, subprocess fixtures, end-to-end tests, and CI integration.
Runner, package, and CI configuration
.github/workflows/*, package.json, packages/*/package.json, packages/*/tsconfig.json, tsconfig*.json
Removes Vitest and Stryker scripts, dependencies, configurations, coverage publication, and platform-specific settings. Adds Bun test globals and Bun-native workflow commands.
Linting and repository guidance
eslint.config.js, CONTRIBUTING.md, evals/README.md, docs/hooks/writing-hooks.md, .github/scripts/issue-planner.ts
Replaces Vitest lint integration with Jest-compatible rules for bun:test and updates testing guidance and examples.
Test compatibility updates
packages/*/*test*, packages/providers/src/auth/*, packages/tools/src/*test*
Updates assertions, parameterized tests, type annotations, package-boundary checks, and pending tests for Bun-compatible execution.

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

Possibly related issues

Possibly related PRs

Suggested labels: ci/cd

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR supports Vitest removal and the guard, but the summary does not demonstrate all four required test-file deletions, lockfile cleanup, or replacements for every retained capability. Delete or verify the remaining required Vitest-only test files, update both lockfiles, and confirm every #2970 acceptance criterion.
Out of Scope Changes check ⚠️ Warning The PR includes numerous test assertion rewrites and skipped-test changes, although #2970 lists changing test assertions as out of scope. Move nonessential assertion and skipped-test edits to a separate PR, or document their direct necessity for the lint migration and obtain scope approval.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 primary change: removing Vitest escape hatches and adding a guard against their return.
Description check ✅ Passed The description includes all template sections, explains the migration, lists linked issues, and provides reviewer commands plus a testing matrix.
✨ 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 issue2970

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 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Before this PR, the repository's ongoing migration from Vitest to Bun's native test runner left several workspaces with retained but unused test:vitest scripts, Vitest configuration files, and documentation references to fallback paths. These escape hatches created a risk of silent regression to Vitest, undermined migration completeness, and made it easy to reintroduce Vitest-specific patterns without failing CI. After this PR, all such escape hatches are removed across the workspace, automated guards and regression tests are added to prevent their return, and the documentation, CI workflows, and package configurations are updated to enforce and reflect the Bun-native testing posture.

Release Notes

Bug Fixes

  • Remove retained Vitest test:vitest escape-hatch scripts and unused Vitest config files across workspaces, eliminating silent fallback paths during the Bun migration

Tests

Documentation

  • Update dev-docs/bun.md, dev-docs/test-runner-inventory.md, dev-docs/RULES.md, and dev-docs/REGRESSION_TESTS.md to remove references to retained Vitest fallbacks
  • Update contributing guide and hook-writing docs to reflect the post-migration testing state

Refactor

  • Remove unused vitest.config.ts files and Vitest-related dependencies from fully migrated workspaces
  • Clean up per-workspace test configurations to rely exclusively on Bun-native execution

Chore

Changes

Layer File(s) Summary
packages/tools/src/tests packages/tools/src/tests/tool-registry-mcp-lazy.test.ts, packages/tools/src/tests/package-boundary.test.ts, packages/tools/src/tests/apply-patch-ax.bun.test.ts, packages/tools/src/tests/tool-key-storage.test.ts, packages/tools/src/tests/shell-tool.test.ts, packages/tools/src/tests/todo-contract.test-d.ts Changes in packages/tools/src/tests
packages/storage packages/storage/tsconfig.json, packages/storage/package.json, packages/storage/vitest.config.ts, packages/storage/vitest.config.fallback-behavior.ts, packages/storage/test-setup-bun-session-reset.ts, packages/storage/vitest.config.native-keyring.ts Changes in packages/storage
packages/cli/src/config packages/cli/src/config/settings.part6.test.ts, packages/cli/src/config/settings.part2.test.ts, packages/cli/src/config/settings.part5.test.ts, packages/cli/src/config/settings.part7.test.ts, packages/cli/src/config/settings-validation.test.ts, packages/cli/src/config/settings.part4.test.ts, packages/cli/src/config/settings.test.ts, packages/cli/src/config/settings.part3.test.ts, packages/cli/src/config/settingsLoader.trust.test.ts Changes in packages/cli/src/config
. CONTRIBUTING.md, tsconfig.json, bun.lock, package.json, vitest.coverage.ts, package-lock.json, eslint.config.js, tsconfig.scripts.json Changes in .
packages/mcp packages/mcp/vitest.config.ts, packages/mcp/stryker.conf.json, packages/mcp/tsconfig.json, packages/mcp/package.json Changes in packages/mcp
dev-docs dev-docs/npm.md, dev-docs/stryker.md, dev-docs/bun.md, dev-docs/schema-guide.md, dev-docs/PLAN.md, dev-docs/test-runner-inventory.md, dev-docs/RULES.md, dev-docs/REGRESSION_TESTS.md Changes in dev-docs
packages/cli packages/cli/tsconfig.json, packages/cli/package.json, packages/cli/tsconfig.base.json Changes in packages/cli
scripts/tests scripts/tests/genai-enclave-adversarial.test.ts, scripts/tests/issue-2978-launcher-exec-bit.test.ts, scripts/tests/no-vitest-guard-helpers.ts, scripts/tests/no-vitest-guard.test.ts, scripts/tests/affected-test-shards.test.ts, scripts/tests/vitest-coverage.test.ts, scripts/tests/bun-workspaces.test.ts, scripts/tests/ci-secure-store-workflow.test.ts Changes in scripts/tests
packages/providers/src/tests packages/providers/src/tests/LoadBalancingProvider.sessionPrecedence.test.ts Changes in packages/providers/src/tests
docs/hooks docs/hooks/writing-hooks.md Changes in docs/hooks
packages/lsp packages/lsp/vitest.config.ts, packages/lsp/package.json Changes in packages/lsp
packages/providers packages/providers/tsconfig.json, packages/providers/stryker.seam.conf.json, packages/providers/package.json, packages/providers/stryker.conf.json, packages/providers/vitest.config.ts Changes in packages/providers
packages/agents/src/core packages/agents/src/core/client.editor-context.test.ts Changes in packages/agents/src/core
packages/tools packages/tools/vitest.config.ts, packages/tools/tsconfig.json, packages/tools/package.json Changes in packages/tools
packages/settings packages/settings/tsconfig.json, packages/settings/package.json Changes in packages/settings
packages/telemetry packages/telemetry/tsconfig.json, packages/telemetry/package.json Changes in packages/telemetry
packages/vscode-ide-companion packages/vscode-ide-companion/package.json, packages/vscode-ide-companion/tsconfig.json Changes in packages/vscode-ide-companion
packages/policy packages/policy/package.json, packages/policy/tsconfig.json Changes in packages/policy
packages/providers/src/auth/proxy/tests packages/providers/src/auth/proxy/tests/oauth-exchange.spec.ts, packages/providers/src/auth/proxy/tests/oauth-poll.spec.ts Changes in packages/providers/src/auth/proxy/tests
packages/a2a-server/src/agent packages/a2a-server/src/agent/task.factory-migration.integration.test.ts Changes in packages/a2a-server/src/agent
packages/core packages/core/package.json, packages/core/stryker.conf.json, packages/core/vitest.config.ts, packages/core/tsconfig.json Changes in packages/core
scripts scripts/check-settings-boundary.ts, scripts/affected-test-shards.ts, scripts/check-no-vitest.ts, scripts/affected-lint-targets.ts Changes in scripts
packages/tools/src/tools packages/tools/src/tools/github-ops.test.ts, packages/tools/src/tools/line-range-tools-issue3036.bun.test.ts Changes in packages/tools/src/tools
packages/tools/src/tools/ast-edit/tests packages/tools/src/tools/ast-edit/tests/ast-edit-force-flag.test.ts Changes in packages/tools/src/tools/ast-edit/tests
packages/providers/src/runtime/tests packages/providers/src/runtime/tests/profileApplication.lb.contextWindowTimeout.test.ts Changes in packages/providers/src/runtime/tests
packages/a2a-server packages/a2a-server/package.json Changes in packages/a2a-server
packages/providers/src packages/providers/src/package-boundary.test.ts Changes in packages/providers/src
.github/actions/post-coverage-comment .github/actions/post-coverage-comment/action.yml Changes in .github/actions/post-coverage-comment
packages/test-utils/src packages/test-utils/src/test-rig.test.ts, packages/test-utils/src/quota-guard-vitest-integration.test.ts Changes in packages/test-utils/src
packages/auth packages/auth/package.json, packages/auth/tsconfig.json, packages/auth/vitest.config.ts Changes in packages/auth
.github/workflows .github/workflows/ci.yml, .github/workflows/nightly.yml Changes in .github/workflows
packages/cli/src/ui/hooks packages/cli/src/ui/hooks/useShellHistory.test.ts, packages/cli/src/ui/hooks/useKeypress.test.tsx Changes in packages/cli/src/ui/hooks
project-plans project-plans/issue2970-remove-vitest-escape-hatches.md Changes in project-plans
packages/cli/src/ui/contexts packages/cli/src/ui/contexts/KeypressContext.test.tsx Changes in packages/cli/src/ui/contexts
packages/providers/src/anthropic packages/providers/src/anthropic/AnthropicMessageNormalizer.anchorCache.test.ts Changes in packages/providers/src/anthropic
packages/agents/src/compression/tests packages/agents/src/compression/tests/compressionPrefixStability.test.ts Changes in packages/agents/src/compression/tests
packages/ide-integration packages/ide-integration/package.json, packages/ide-integration/tsconfig.json Changes in packages/ide-integration
packages/core/src/integration-tests packages/core/src/integration-tests/provider-settings-integration.test.ts Changes in packages/core/src/integration-tests
scripts/genai-enclave scripts/genai-enclave/config.ts Changes in scripts/genai-enclave
evals evals/README.md Changes in evals
.github/scripts .github/scripts/issue-planner.ts Changes in .github/scripts
packages/providers/src/auth/tests packages/providers/src/auth/tests/oauth-manager.issue913.spec.ts Changes in packages/providers/src/auth/tests
packages/test-utils packages/test-utils/package.json Changes in packages/test-utils

Magnitude

🎯 5 (XXL)
2832 additions, 3297 deletions, 112 changed files across 16 packages, 22 acceptance criteria

Related


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

"test:bun": "bun ../../scripts/run_bun_tests.ts --workspace storage",
"test:ci": "bun ../../scripts/run_bun_tests.ts --workspace storage --junit junit.xml",
"test:vitest": "vitest run",
"typecheck": "tsc --noEmit",

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.

[bug/high] Root override logic is inverted: the script defaults to the real repository, not the override. This makes the temporary root guard (NO_VITEST_ROOT) unusable for the behavioral tests added in this PR, and it also means CI always scans the whole tree even when an override is supplied.

Bug: resolve(NO_VITEST_ROOT ? ... : fallback) uses the override truthy/falsy test the wrong way round. Correct: const REPO_ROOT = process.env.NO_VITEST_ROOT ? resolve(...override...) : resolve(...fallback...);.

Comment thread scripts/check-no-vitest.ts
Comment thread scripts/check-no-vitest.ts
Comment thread scripts/check-no-vitest.ts Outdated
Comment thread scripts/check-no-vitest.ts Outdated
Comment thread scripts/check-no-vitest.ts
Comment thread scripts/tests/no-vitest-guard.test.ts
Comment thread scripts/tests/no-vitest-guard.test.ts
Comment thread scripts/tests/no-vitest-guard.test.ts
Comment thread scripts/tests/no-vitest-guard.test.ts
Comment thread scripts/tests/no-vitest-guard.test.ts
@github-actions

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

…ere widening

Removing the vitest devDependency removed the looser declarations that were
masking two type-aware lint errors in the CLI test suite. Under bun-types the
callback parameters of describe.each and it.each are 'any', so:

  - useKeypress.test.tsx: 'useKitty' in a conditional tripped
    strict-boolean-expressions.
  - KeypressContext.test.tsx: 'writeSequence' returned 'any', which made
    act(() => writeSequence(...)) look like a floating promise even though the
    table's implementations all return void.

Both are annotated with the types the table already supplies. No behavior
changes; the surrounding reformatting is prettier reflowing the wrapped calls.

Caught by the full-tree lint:ci run that this PR triggers (a workflow change
forces lint full rather than scoped). Local runs missed it because the working
node_modules still contained vitest.
Removing the vitest devDependency broke npm run typecheck with ~500 errors:

    error TS2593: Cannot find name 'describe'.
    error TS2304: Cannot find name 'beforeEach'.

Every tsconfig sets types: ['node', 'bun-types/test'], and bun-types/test.d.ts
declares only the bun:test MODULE. The ambient globals live in a separate file,
bun-types/test-globals.d.ts, which that entry never loads. Vitest's presence was
masking the gap, so removing it exposed a latent misconfiguration rather than
creating one. All 14 tsconfigs now load both.

Nine files under packages/cli/src/config still carried a literal
/// <reference types="vitest/globals" /> directive. Those are removed, and the
guard is extended to catch triple-slash references -- it previously matched only
import and require forms, which is why it passed over them. Three positive cases
and a prose negative control cover the new detection.

useShellHistory.test.ts needed a real fix rather than a type widening: bun's
expect() infers its matcher type from the received value, so
'let command: string | null = null' narrowed to the null literal and .toBe('cmd2')
had no matching overload. The declarations now use a definite-assignment
assertion, keeping the declared type and the assertions unchanged.

This was invisible locally because the working node_modules still contained
vitest; it only appears on a clean install from the lockfile.
Ten findings from the PR review, all on scripts/check-no-vitest.ts.

The guard documented 'no false positives on prose' but applied the import
regex to raw file content, so a commented-out example or a migration note in a
string literal failed CI. Matches are now skipped when they fall inside a
comment, string or template literal. The masking computes character spans
rather than deleting text, so the reported file:line stays accurate, and
template interpolation is still treated as real code. Triple-slash references
are deliberately exempt from the masking: that directive is a comment by
syntax and must keep failing.

Detection gaps closed: the binary patterns caught 'npx vitest' but not
'npm run vitest', 'pnpm exec vitest', 'yarn run vitest', 'bun run vitest' or
'bunx --bun vitest', all of which are ordinary ways to invoke it from a
workflow or Makefile. The TOML pattern also required a file extension, missing
a bare preload of './vitest', and lacked a word boundary, so an unrelated
'avitest-shim.ts' was reported as a violation.

parseManifest swallowed JSON parse errors and returned undefined, which made
scanManifest skip the file: a malformed package.json declaring vitest passed
silently. It now reports through the operational-error path and fails.

Seventeen behavioral cases added, covering each new detection and each new
no-false-positive case.

One review finding is rejected: the NO_VITEST_ROOT override was reported as an
inverted ternary. It is not inverted, and fixture trees demonstrably resolve to
the override while the real repository resolves to the fallback.
@acoliver

acoliver commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Review findings addressed in e30ed7e

Ten of the eleven findings are fixed; one is rejected with evidence.

False positives on comments and string literals (4 threads). Correct, and it contradicted the guard's own stated design principle. The import scan now skips matches falling inside a line comment, block comment, string or template literal. The masking computes character spans instead of deleting text, so the reported file:line stays accurate, and template interpolation is still treated as real code so a genuine import inside ${...} still fires. Triple-slash /// <reference types="vitest..." /> is deliberately exempt — that directive is a comment by syntax and must keep failing.

Missing binary invocation forms (2 threads). Correct. npm run vitest, pnpm exec vitest, yarn exec vitest, yarn run vitest, bun run vitest and bunx --bun vitest are now detected in workflow YAML, shell scripts and Makefiles.

TOML word boundary (2 threads). Correct — avitest-shim.ts and myvitest-helper.ts were being reported as violations. Fixed.

TOML bare reference. Correct — a preload = ["./vitest"] with no extension was missed. Fixed.

parseManifest swallowing JSON errors. Correct, and the most serious of the set: a malformed package.json declaring a vitest dependency passed silently, defeating the fail-closed posture. Parse failures now surface through the operational-error path and fail the guard.

Seventeen behavioral cases were added, one per new detection and one per new no-false-positive case. The guard test suite is now 62 cases.

Rejected: the NO_VITEST_ROOT ternary is not inverted

The finding claims resolve(NO_VITEST_ROOT ? ... : fallback) has the override test the wrong way round, and proposes const REPO_ROOT = process.env.NO_VITEST_ROOT ? resolve(...override...) : resolve(...fallback...).

That proposed form is already what the code does:

const REPO_ROOT = process.env.NO_VITEST_ROOT
  ? resolve(process.env.NO_VITEST_ROOT)
  : resolve(dirname(fileURLToPath(import.meta.url)), '..');

Verified behaviourally as well: a fixture tree containing a violation exits 1 while the real repository exits 0 in the same run, which is only possible if the override is honoured. (The comment was also attached to packages/storage/package.json:82, which is not where this code lives.)

Verification

Full attack matrix re-proved after the false-positive work, to confirm the leniency did not blind any real detection — 19 must-fail cases all exit 1, 9 must-pass cases all exit 0. Plus lint:ci, lint:eslint-guard, lint:no-vitest, typecheck, format:check, build and test:scripts all green.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/package.json (1)

541-541: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add a required mutation-testing gate

No replacement exists for test:mutation. Add a Bun-compatible mutation script and invoke it from a required CI job before merge.

🤖 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/package.json` at line 541, Add a Bun-compatible test:mutation
script in package.json, then update the CI workflow to run it in a required
pre-merge job. Ensure the job invokes the package script and is configured as a
mandatory status check before merging.
🧹 Nitpick comments (3)
scripts/tests/no-vitest-guard.test.ts (1)

896-905: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicated real-repository scan.

This test calls runScriptRealRepo(0) with the same assertion as the test at Lines 56-60. It runs a second full-repository walk and adds up to 90 seconds without new coverage. The comment already states that the earlier test proves the contract. Either delete this test, or replace it with a fixture that reproduces the self-excluded filenames and asserts they are ignored.

♻️ Proposed fixture-based replacement
     describe('self-exclusion', () => {
-      it('does not flag its own source or test fixtures', () => {
-        const { code } = runScriptRealRepo(0);
-        expect(code).toBe(0);
-      }, 90_000);
+      it('ignores the guard source and its helper files', () => {
+        const { code } = withFixture(({ root, write }) => {
+          write(
+            'scripts/check-no-vitest.ts',
+            "import { it } from 'vitest';\nexport const x = 1;\n",
+          );
+          write(
+            'scripts/tests/no-vitest-guard-helpers.ts',
+            "import { it } from 'vitest';\nexport const y = 1;\n",
+          );
+          return runScript(root, 0);
+        });
+        expect(code).toBe(0);
+      });
     });
🤖 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/no-vitest-guard.test.ts` around lines 896 - 905, Remove the
duplicate self-exclusion test that calls runScriptRealRepo(0), since the earlier
real-repository test already covers this behavior. If retaining coverage in the
describe('self-exclusion') block, replace the full-repository scan with a
focused fixture containing self-excluded filenames and assert the guard ignores
them.
scripts/check-no-vitest.ts (1)

554-594: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Hoist the global-flagged regexes to module scope.

scanCodeFile runs for every code file in the tree. Each call compiles two new RegExp objects from .source. Declare the global variants once at module scope and reset lastIndex before each scan. This removes per-file compilation and keeps the two pattern definitions adjacent to their non-global documentation.

♻️ Proposed refactor
+const VITEST_IMPORT_PATTERN_G = new RegExp(VITEST_IMPORT_PATTERN.source, 'g');
+const VITEST_TRIPLE_SLASH_REFERENCE_G = new RegExp(
+  VITEST_TRIPLE_SLASH_REFERENCE.source,
+  'gm',
+);
+
 function scanCodeFile(filePath: string, content: string): Violation[] {
   const violations: Violation[] = [];
   const maskedSpans = computeMaskedSpans(content);
-  const globalPattern = new RegExp(VITEST_IMPORT_PATTERN.source, 'g');
+  const globalPattern = VITEST_IMPORT_PATTERN_G;
+  globalPattern.lastIndex = 0;
🤖 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/check-no-vitest.ts` around lines 554 - 594, Move the global RegExp
variants currently created inside scanCodeFile—globalPattern and refPattern—to
module scope adjacent to their corresponding VITEST pattern definitions. Reuse
these shared regexes in scanCodeFile and reset each regex’s lastIndex to 0
before its scan loop, preserving the existing matching and empty-match
safeguards.
package.json (1)

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

Pin eslint-plugin-jest to 28.14.0. This version supports ESLint 9.29.0 and flat configuration.

🤖 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 `@package.json` at line 240, Update the eslint-plugin-jest dependency
declaration in package.json from the range to the exact version 28.14.0,
preserving the existing dependency key and JSON formatting.
🤖 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/src/compression/__tests__/compressionPrefixStability.test.ts`:
- Around line 473-474: Update the assertion in the compression prefix stability
test to reject any preserved response when its matching call is absent: require
responseIndex to be invalid or require callIndex to be present and precede
responseIndex. Preserve acceptance for responses with a valid preceding call and
for entries with no response.

In `@packages/tools/src/tools/ast-edit/__tests__/ast-edit-force-flag.test.ts`:
- Line 61: Update the test assertion around readFileSync to capture the thrown
error, then assert its code with toMatchObject({ code: 'ENOENT' }); do not pass
expect.objectContaining to toThrow, since Bun does not support that matcher
argument.

---

Outside diff comments:
In `@packages/core/package.json`:
- Line 541: Add a Bun-compatible test:mutation script in package.json, then
update the CI workflow to run it in a required pre-merge job. Ensure the job
invokes the package script and is configured as a mandatory status check before
merging.

---

Nitpick comments:
In `@package.json`:
- Line 240: Update the eslint-plugin-jest dependency declaration in package.json
from the range to the exact version 28.14.0, preserving the existing dependency
key and JSON formatting.

In `@scripts/check-no-vitest.ts`:
- Around line 554-594: Move the global RegExp variants currently created inside
scanCodeFile—globalPattern and refPattern—to module scope adjacent to their
corresponding VITEST pattern definitions. Reuse these shared regexes in
scanCodeFile and reset each regex’s lastIndex to 0 before its scan loop,
preserving the existing matching and empty-match safeguards.

In `@scripts/tests/no-vitest-guard.test.ts`:
- Around line 896-905: Remove the duplicate self-exclusion test that calls
runScriptRealRepo(0), since the earlier real-repository test already covers this
behavior. If retaining coverage in the describe('self-exclusion') block, replace
the full-repository scan with a focused fixture containing self-excluded
filenames and assert the guard ignores them.
🪄 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: e17a340b-27c5-4f4d-a7c1-9ac3fbff69bd

📥 Commits

Reviewing files that changed from the base of the PR and between 9658f64 and e30ed7e.

⛔ Files ignored due to path filters (11)
  • bun.lock is excluded by !**/*.lock, !**/*.lock
  • dev-docs/PLAN.md is excluded by !dev-docs/**
  • dev-docs/REGRESSION_TESTS.md is excluded by !dev-docs/**
  • dev-docs/RULES.md is excluded by !dev-docs/**
  • dev-docs/bun.md is excluded by !dev-docs/**
  • dev-docs/npm.md is excluded by !dev-docs/**
  • dev-docs/schema-guide.md is excluded by !dev-docs/**
  • dev-docs/stryker.md is excluded by !dev-docs/**
  • dev-docs/test-runner-inventory.md is excluded by !dev-docs/**
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
  • project-plans/issue2970-remove-vitest-escape-hatches.md is excluded by !project-plans/**
📒 Files selected for processing (99)
  • .github/actions/post-coverage-comment/action.yml
  • .github/scripts/issue-planner.ts
  • .github/workflows/ci.yml
  • .github/workflows/nightly.yml
  • CONTRIBUTING.md
  • docs/hooks/writing-hooks.md
  • eslint.config.js
  • evals/README.md
  • package.json
  • packages/a2a-server/package.json
  • packages/a2a-server/src/agent/task.factory-migration.integration.test.ts
  • packages/agents/src/compression/__tests__/compressionPrefixStability.test.ts
  • packages/agents/src/core/client.editor-context.test.ts
  • packages/auth/package.json
  • packages/auth/tsconfig.json
  • packages/auth/vitest.config.ts
  • packages/cli/package.json
  • packages/cli/run-bun-tests.ts
  • packages/cli/src/config/settings-validation.test.ts
  • packages/cli/src/config/settings.part2.test.ts
  • packages/cli/src/config/settings.part3.test.ts
  • packages/cli/src/config/settings.part4.test.ts
  • packages/cli/src/config/settings.part5.test.ts
  • packages/cli/src/config/settings.part6.test.ts
  • packages/cli/src/config/settings.part7.test.ts
  • packages/cli/src/config/settings.test.ts
  • packages/cli/src/config/settingsLoader.trust.test.ts
  • packages/cli/src/ui/contexts/KeypressContext.test.tsx
  • packages/cli/src/ui/hooks/useKeypress.test.tsx
  • packages/cli/src/ui/hooks/useShellHistory.test.ts
  • packages/cli/tsconfig.base.json
  • packages/cli/tsconfig.json
  • packages/core/package.json
  • packages/core/src/integration-tests/provider-settings-integration.test.ts
  • packages/core/stryker.conf.json
  • packages/core/tsconfig.json
  • packages/core/vitest.config.ts
  • packages/ide-integration/package.json
  • packages/ide-integration/tsconfig.json
  • packages/lsp/package.json
  • packages/lsp/vitest.config.ts
  • packages/mcp/package.json
  • packages/mcp/stryker.conf.json
  • packages/mcp/tsconfig.json
  • packages/mcp/vitest.config.ts
  • packages/policy/package.json
  • packages/policy/tsconfig.json
  • packages/providers/package.json
  • packages/providers/src/anthropic/AnthropicMessageNormalizer.anchorCache.test.ts
  • packages/providers/src/auth/__tests__/oauth-manager.issue913.spec.ts
  • packages/providers/src/auth/proxy/__tests__/oauth-exchange.spec.ts
  • packages/providers/src/auth/proxy/__tests__/oauth-poll.spec.ts
  • packages/providers/src/package-boundary.test.ts
  • packages/providers/stryker.conf.json
  • packages/providers/stryker.seam.conf.json
  • packages/providers/tsconfig.json
  • packages/providers/vitest.config.ts
  • packages/settings/package.json
  • packages/settings/tsconfig.json
  • packages/storage/package.json
  • packages/storage/test-setup-bun-session-reset.ts
  • packages/storage/tsconfig.json
  • packages/storage/vitest.config.fallback-behavior.ts
  • packages/storage/vitest.config.native-keyring.ts
  • packages/storage/vitest.config.ts
  • packages/telemetry/package.json
  • packages/telemetry/tsconfig.json
  • packages/test-utils/package.json
  • packages/test-utils/src/quota-guard-vitest-integration.test.ts
  • packages/test-utils/src/test-rig.test.ts
  • packages/tools/package.json
  • packages/tools/src/__tests__/apply-patch-ax.bun.test.ts
  • packages/tools/src/__tests__/package-boundary.test.ts
  • packages/tools/src/__tests__/shell-tool.test.ts
  • packages/tools/src/__tests__/todo-contract.test-d.ts
  • packages/tools/src/__tests__/tool-key-storage.test.ts
  • packages/tools/src/__tests__/tool-registry-mcp-lazy.test.ts
  • packages/tools/src/tools/ast-edit/__tests__/ast-edit-force-flag.test.ts
  • packages/tools/src/tools/github-ops.test.ts
  • packages/tools/src/tools/line-range-tools-issue3036.bun.test.ts
  • packages/tools/tsconfig.json
  • packages/tools/vitest.config.ts
  • packages/vscode-ide-companion/package.json
  • packages/vscode-ide-companion/tsconfig.json
  • scripts/affected-lint-targets.ts
  • scripts/affected-test-shards.ts
  • scripts/check-no-vitest.ts
  • scripts/check-settings-boundary.ts
  • scripts/genai-enclave/config.ts
  • scripts/tests/affected-test-shards.test.ts
  • scripts/tests/bun-workspaces.test.ts
  • scripts/tests/ci-secure-store-workflow.test.ts
  • scripts/tests/genai-enclave-adversarial.test.ts
  • scripts/tests/no-vitest-guard-helpers.ts
  • scripts/tests/no-vitest-guard.test.ts
  • scripts/tests/vitest-coverage.test.ts
  • tsconfig.json
  • tsconfig.scripts.json
  • vitest.coverage.ts
💤 Files with no reviewable changes (28)
  • packages/tools/package.json
  • packages/cli/src/config/settings.part6.test.ts
  • packages/lsp/vitest.config.ts
  • packages/core/vitest.config.ts
  • packages/providers/stryker.seam.conf.json
  • packages/cli/src/config/settings.part5.test.ts
  • packages/cli/src/config/settings.part2.test.ts
  • packages/cli/src/config/settings.test.ts
  • packages/mcp/stryker.conf.json
  • packages/storage/vitest.config.fallback-behavior.ts
  • packages/cli/src/config/settingsLoader.trust.test.ts
  • packages/test-utils/src/quota-guard-vitest-integration.test.ts
  • packages/storage/vitest.config.ts
  • packages/providers/stryker.conf.json
  • packages/core/stryker.conf.json
  • packages/cli/src/config/settings-validation.test.ts
  • packages/cli/src/config/settings.part4.test.ts
  • .github/actions/post-coverage-comment/action.yml
  • packages/storage/vitest.config.native-keyring.ts
  • packages/providers/vitest.config.ts
  • packages/auth/vitest.config.ts
  • packages/cli/src/config/settings.part7.test.ts
  • scripts/tests/vitest-coverage.test.ts
  • packages/mcp/vitest.config.ts
  • vitest.coverage.ts
  • packages/tools/vitest.config.ts
  • packages/cli/src/config/settings.part3.test.ts
  • scripts/affected-lint-targets.ts

Comment thread packages/tools/src/tools/ast-edit/__tests__/ast-edit-force-flag.test.ts Outdated
@acoliver acoliver closed this Aug 8, 2026
@acoliver acoliver reopened this Aug 8, 2026
The pull_request workflows did not schedule for the previous two pushes,
although the pull_request_target ones did.
# Conflicts:
#	packages/cli/run-bun-tests.ts
Review point: the file-not-created check matched /ENOENT/ against the error
message, but the message wording is not part of Node's contract -- the code
property is. Capturing the error and matching on { code: 'ENOENT' } asserts the
documented surface, and still fails loudly if the file was created, because the
captured error is then undefined.
profileApplication.lb.contextWindowTimeout.test.ts arrived from main after
this PR enabled jest/no-conditional-expect at error, and it wraps its
assertion in the type guard it already asserted one line above.

Converted to an early return rather than folding the guard into the assertion:
it keeps expect out of a branch, still narrows the union for the compiler, and
preserves the failure message, which the boolean-collapse form loses.
Merged from main after this PR enabled jest/require-to-throw-message at error.
Writing to a frozen object throws a TypeError by spec, so the class is the
stable assertion; the message wording differs between engines.
scripts/tests/issue-2978-launcher-exec-bit.test.ts arrived from main (#3086)
importing the vitest specifier, which the lint:no-vitest guard this PR adds
rejects. This is exactly the regression the guard exists to prevent, and it
caught it on the merge.

Only the import changed. The file's one failing assertion (git ls-files
returning empty) fails identically with main's version in the same
environment: execFileSync does not reliably capture a child's stdout under
bun test here, which is the same limitation that made the guard's own test
helper write to a file instead of a pipe. Not introduced by this change.
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.

Remove all Vitest escape hatches: scripts, configs, deps, lint plugin, and CI guard (#2578)

1 participant