Skip to main content

pnpm-store-project-mirror-breaks-vitest-glob

After `pnpm install` in a git worktree, vitest/jest can discover a test file from a SECOND path โ€” pnpm hardlinks the whole project (test/ included) into `~/Library/pnpm/store/v<n>/projects/<hash>/`, and the glob picks up that out-of-root mirror where the `@/` alias / vite root doesn't apply โ†’ bogus "Cannot find package '@/...'" resolution errors. Clearing the stale store-project mirror fixes it.

Jump to install

Source facts

Repository
blas1n/claude-skills
Last source activity
June 25, 2026 at 06:07
Detected SKILL.md language
English
Stars
2
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions ยท Read-only preview
name
pnpm-store-project-mirror-breaks-vitest-glob
description
After `pnpm install` in a git worktree, vitest/jest can discover a test file from a SECOND path โ€” pnpm hardlinks the whole project (test/ included) into `~/Library/pnpm/store/v<n>/projects/<hash>/`, and the glob picks up that out-of-root mirror where the `@/` alias / vite root doesn't apply โ†’ bogus "Cannot find package '@/...'" resolution errors. Clearing the stale store-project mirror fixes it.
when_to_use
A worktree's test suite suddenly fails to RESOLVE imports (`Cannot find package '@/...'`, alias not applying) for a NEW or just-touched test file while sibling tests in the same dir pass โ€” especially right after a fresh `pnpm install` / `pnpm install --frozen-lockfile` in that worktree. Also when a test "Failed Suite" is reported from a `~/Library/pnpm/store/.../projects/<hash>/...` path you never wrote to.
languages
["typescript","javascript"]
frameworks
["vitest","jest","pnpm","vite","next.js"]
# pnpm's project hardlink mirror makes vitest glob a test from outside the root ## Symptom In a git worktree, after `pnpm install`, the test runner reports the SAME test file failing from two paths โ€” the real one and a store path: ``` FAIL apps/pwa/test/foo.test.tsx FAIL Library/pnpm/store/v10/projects/<hash>/test/foo.test.tsx Error: Cannot find package '@/components/...' imported from .../test/foo.test.tsx ``` Sibling tests in the same directory (with the identical `import X from "@/..."`) pass. Only a **new or recently-written** test file trips it. tsc/biome are fine; it's purely a test-discovery/alias-resolution error. ## Root cause `pnpm install` content-addresses the project and **hardlinks the entire project tree โ€” including `test/` โ€” into `~/Library/pnpm/store/v<n>/projects/<hash>/`** (the per-project side-effects store, hash derived from the project). Those are hardlinks (same inode), not symlinks, so they're real files at a second path. The test runner's file glob (vitest `include: ["test/**/*.test.{ts,tsx}"]`, resolved against the vite `root`) ends up discovering the file via that out-of-root store path. There, the `@` alias โ€” defined as `fileURLToPath(new URL("./", import.meta.url))` = the project dir โ€” and vite's "allowed root" don't cover the store location, so `@/...` resolves to nothing โ†’ "Cannot find package". The reason a *fresh* file triggers it is link/glob ordering; older files happen to resolve to the in-root copy. Confirm with an inode compare: ```bash ls -i test/foo.test.tsx ls -i ~/Library/pnpm/store/v10/projects/<hash>/test/foo.test.tsx # SAME inode โ†’ hardlink mirror ``` ## Fix Delete the stale store-project mirror. It's hardlinks โ€” removing the store copy does NOT touch your real working-tree files (the other hardlink survives): ```bash rm -rf ~/Library/pnpm/store/v10/projects/<hash> npx vitest run test/foo.test.tsx # now a single, in-root path โ†’ @/ resolves ``` (Find `<hash>` from the failing path in the runner output, or `ls ~/Library/pnpm/store/v*/projects/`.) ## Prevention - **Run the test suite against a symlinked `node_modules`, not a fresh in-worktree `pnpm install`.** Symlinking the worktree's `node_modules` to a sibling worktree that has the same deps (e.g. `ln -s ../../main/apps/pwa/node_modules node_modules`) avoids creating the project store mirror entirely. Do the real `pnpm install` only when you need a real `node_modules` for the production build (`next build` rejects the symlink โ€” see below), and clear the mirror afterward if you then run tests. - The symlink trick is also why `next build` (Turbopack) fails with "Symlink node_modules is invalid, it points out of the filesystem root" โ€” so the usual pattern is: **symlink for tests, real install for build.** Just remember the real install plants the store mirror. - This is worktree-specific: multiple worktrees of the same package share the same project hash, so one worktree's install can leave a mirror that another worktree's runner then globs. ## Generalization Any content-addressed package manager (pnpm, and Bun's global cache) can create hardlinked copies of your source outside the project root. When a glob-based tool (test runner, linter, bundler) reports a file from a `store`/`cache` path you never wrote, suspect a hardlink mirror โ€” `ls -i` to confirm the shared inode, and delete the out-of-root copy rather than your real files.
View on GitHub