fix(web): let vitest run from a checkout inside a .git directory#879
Open
aelefebv wants to merge 1 commit into
Open
fix(web): let vitest run from a checkout inside a .git directory#879aelefebv wants to merge 1 commit into
aelefebv wants to merge 1 commit into
Conversation
Vite's default `server.fs.deny` includes `**/.git/**` so a server never hands repository internals to a client. That default assumes the project is not itself inside a `.git` directory. When it is — a linked worktree under `.git/` is an ordinary layout — the project's own files match the pattern, and every test file running in a browser-like environment fails to load with `Cannot find module '/src/...'`. From such a checkout the suite reported 49 of 153 files red for reasons unrelated to the code. Re-anchor that rule to the checkout root when running under Vitest with `--mode test` from such a checkout, rather than dropping it. Test files load again, and a real `.git` directory *inside* the checkout stays denied: without the anchor its `config` — and any credential embedded in a remote URL — is served in full. The surrounding `.git` directory can't be handled by a glob, because it is a parent of the checkout and denying it denies the checkout too. Its reachability is set by `server.fs.allow`, into which Vitest unions its own entries — `dirname(configFile)`, `searchForWorkspaceRoot(root)` and its dist dir — alongside the `allow: ['..']` set here. A union is never narrower, so those entries can only widen it. They land inside the checkout today only because no workspace marker exists above it; adding a `pnpm-workspace.yaml` at the repo root would widen test-time file serving to include the outer `.git`. Rather than depend on that silently, the config resolves the workspace root itself and fails with an explanatory error — one that also offers a way out for a reader whose checkout location isn't theirs to change. Gating on `mode === 'test'` costs one thing, noted in the wiki: running Vitest with a non-test `--mode` from such a checkout brings the original failure back. It buys immunity from a stray exported `VITEST=1` relaxing `pnpm dev`, which is the better trade. Unchanged: `.env`, `.env.*` and key material stay denied in tests too, `fs.strict` stays on, and an ordinary checkout never takes the override in any mode. Two things the wiki page is explicit about rather than glossing: a test run is not serverless (`vitest --api`/`--ui` expose the same pipeline over HTTP), and everything under the checkout is reachable through it, not just source and tests. From this worktree: 49 failed / 104 passed (1606 tests) before, 153 passed (2198 tests) after. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes
lucida-bfk.The problem
Running the web suite from a checkout that lives inside a
.gitdirectory failed 49 of 153 test files. Two independent mechanisms: vitest's built-in exclude, and Vite'sserver.fs.deny, whose default includes**/.git/**. Any path containing a.gitsegment is rejected, so the happy-dom test files could not be loaded at all — a spurious failure with nothing wrong in the code.This is not hypothetical housekeeping. Orchestrated worktrees are placed under
.git/, which made the whole web toolchain unusable from them.The fix
vite.config.tsreplaces the default**/.git/**deny rule with one anchored to the checkout root, so the surrounding.gitno longer blocks loads while any.gitinside the tree stays protected. The override is narrowly gated: it applies only whenVITESTis set and the mode istestand the checkout is actually inside a.gitpath. An ordinary checkout never takes it, in any mode.There is also a startup guard: if the resolved workspace root escapes the checkout, the config throws rather than silently widening what the dev server will serve.
How it was verified
Independent review measured a seven-row live-server matrix, including a negative control the first attempt lacked — an ordinary checkout with a real sibling
.gitinside the allow root, which is the only row that could catch an over-broad override. The override is inert in five of six standard rows, active only for the intended one, and does not reach the sibling.git.The pre-fix state leaked file contents through
/@fs/<checkout>/.git/configon both the dev-server andvitest --apisurfaces; all such requests are 403 after..env,.env.*,*.{crt,pem}andfs.strictremain in force everywhere.The new guard cannot fire in this repository today: there is no workspace marker from the checkout root upward and no
workspacesfield in the rootpackage.json.Gates under Node 24 (matching CI's
lts/*): 153 files / 2198 tests, lint,tsc -b, andvite buildall clean.Known and documented, not fixed
Running vitest with a non-test
--modefrom such a checkout brings the original failure back. That invocation was already broken before this change, and themode === 'test'condition is what stops a stray exportedVITEST=1from relaxingpnpm dev. It is written down in the gotcha page.🤖 Generated with Claude Code