You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: judge nested node_modules symlinks, and correct the escape advice
`worktree:link` plants a symlink per workspace that carries its own tree,
eight in today's layout, and every escape-path remedy in this PR said `rm
node_modules`, singular. Following that advice removes the root link, leaves
seven live links into the primary, and the next install writes through the
nested ones, the same corruption one level down. The gate had the same blind
spot: it judged only the target's root node_modules, so with a real root tree
and a nested symlink still standing, `npm install` was allowed.
The hook now finds a nested node_modules symlink under the target and the git
toplevel (find without -L neither follows nor descends symlinks, and real
node_modules trees are pruned, so it is a handful of directory reads), and
every advice site names the plural remedy:
find . -maxdepth 4 -type l -name node_modules -delete
Sites corrected: the hook message, the link script docblock, the preinstall
reporter, the doctor reinstall remedy, and AGENTS.md.
Copy file name to clipboardExpand all lines: AGENTS.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,7 +64,7 @@ Fix it with **`npm run worktree:link`** from inside the worktree. A full `npm in
64
64
65
65
The script discovers the `node_modules` set from the primary checkout rather than hardcoding a list (it changes whenever a package gains a nested tree), never overwrites an existing path, and never creates a dangling link, so it is safe to re-run and safe in a worktree where you already ran a real `npm install`. The seed step keeps the same contract: it only ever applies pending migrations and inserts demo rows that are not there, so a database with rows in it is left untouched.
66
66
67
-
**Know what this does NOT give you.** The worktree then runs the PRIMARY checkout's framework source through every bare `@webjsdev/*` specifier, because `<primary>/node_modules/@webjsdev/core` is a relative symlink into `<primary>/packages/core` and resolving through the linked root lands there. Relative imports (`../../../src/x.js`) and the browser suite, which web-test-runner serves from the worktree, do use the worktree's own files. So linking makes the suite RUNNABLE, not self-testing: if you are editing `packages/core/src` or `packages/server/src` and need a bare-specifier consumer to exercise YOUR copy, delete the`node_modules` SYMLINK first (`rm node_modules`, it is only a link and nothing else is lost) and then install, or repoint the individual `@webjsdev/<pkg>` entries at it. CI always builds from the branch, so it is unaffected either way.
67
+
**Know what this does NOT give you.** The worktree then runs the PRIMARY checkout's framework source through every bare `@webjsdev/*` specifier, because `<primary>/node_modules/@webjsdev/core` is a relative symlink into `<primary>/packages/core` and resolving through the linked root lands there. Relative imports (`../../../src/x.js`) and the browser suite, which web-test-runner serves from the worktree, do use the worktree's own files. So linking makes the suite RUNNABLE, not self-testing: if you are editing `packages/core/src` or `packages/server/src` and need a bare-specifier consumer to exercise YOUR copy, delete EVERY`node_modules` SYMLINK first, not only the root one, because the link script plants one per workspace that carries its own tree (`find . -maxdepth 4 -type l -name node_modules -delete`, they are only links and nothing else is lost) and then install, or repoint the individual `@webjsdev/<pkg>` entries at it. CI always builds from the branch, so it is unaffected either way.
68
68
69
69
**NEVER install while the `node_modules` symlink is standing (#1442).** This is the trap the two paragraphs above used to walk you into, and the damage lands on a checkout you are not working in, so the failure surfaces in someone else's session with nothing naming the cause. Measured on npm 11.19.0 and bun 1.3.14: `npm ci` DELETES the primary's whole `node_modules` through the link before any lifecycle script runs, `bun install` writes packages and `.bin` entries straight into the primary through it, and `npm install` silently replaces the link with a real tree, detaching the worktree from the shared source. No `preinstall` script can prevent any of it, because npm removes the symlink before `preinstall` runs, `npm ci` has already emptied the primary by then, and Bun runs it in time but ignores a non-zero exit. So the layers are: Claude Code BLOCKS the command through `.claude/hooks/block-install-in-linked-worktree.sh`, which covers every manager's install aliases plus the REMOVE verbs (`npm rm` in a linked worktree deletes from the owning checkout), judges a COMMAND rather than a token so `git commit -m "fix: npm install ..."` and `grep -rn "npm ci"` are unaffected, and never blocks a GLOBAL `-g` install such as the post-release `npm update -g webjsdev` (escape hatch `WEBJS_NO_WORKTREE_INSTALL_GATE=1`), the root `preinstall` REPORTS it for every other tool without ever blocking, `npm run worktree:link` REPAIRS an already-damaged primary, and `npm run check:worktree-links` reports what it would repair without changing anything, exiting non-zero when there is work. `WEBJS_NO_WORKTREE_REPAIR=1` suppresses the repair WRITE, so it has no effect on `--check`, which never writes and always inspects. Tests: `test/hooks/block-install-in-linked-worktree.test.mjs`, `test/repo-health/warn-worktree-install.test.mjs`, `test/repo-health/link-worktree-deps.test.mjs`.
0 commit comments