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: keep quoted paths, drop heredoc bodies, split the verb tables
The previous commit's quote handling DELETED quoted spans, which took the path
with them. `cd "<worktree>" && npm ci` left `cd` with no argument, so the
install was judged against the session cwd and allowed. That is the arrival
shape this hook exists for, and quoting a path is the ordinary spelling, so the
rewrite regressed the headline case.
Quoted spans are now neutralised rather than removed: the quote characters go,
the content stays, and only the separators inside them are defused. It is a
character-by-character state machine because quote nesting has to be tracked,
and a single-quote sed pass running first paired the apostrophe in `can't` with
the next quote in the line and swallowed a real install between them.
Heredoc bodies are dropped. This repo's docs are full of `npm install` lines
and a newline is a command separator here.
The merged verb table is per manager again: `a` is a BUN alias, so merging it
blocked `npm --workspace a run build`, and `bun upgrade` upgrades the Bun
binary rather than node_modules. `npm audit fix` joins the block list on the
same rationale as `link`, `rebuild` and `prune`, `--cwd` and `--dir` are mined
as target directories like `--prefix`, and an informational `yarn --version` is
no longer read as a bare install.
Copy file name to clipboardExpand all lines: framework-dev.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,9 +162,9 @@ Measured on npm 11.19.0 and bun 1.3.14:
162
162
163
163
So prevention lives one layer up, and the rest is repair:
164
164
165
-
-**Block.**`.claude/hooks/block-install-in-linked-worktree.sh` is a `PreToolUse` (Bash) hook, the only layer that sees the state before the package manager starts. It refuses an install verb whose target directory has a symlinked `node_modules`, covering every manager's documented aliases (`bun i` matters most, since Bun writes THROUGH the link) and the REMOVE verbs too, because `npm rm` in a linked worktree deletes from the checkout that owns the tree. Escape hatch `WEBJS_NO_WORKTREE_INSTALL_GATE=1`.
165
+
-**Block.**`.claude/hooks/block-install-in-linked-worktree.sh` is a `PreToolUse` (Bash) hook, the only layer that sees the state before the package manager starts. It refuses an install verb whose target directory has a symlinked `node_modules`, covering every manager's documented aliases (`bun i` matters most, since Bun writes THROUGH the link), the REMOVE verbs, and `link` / `rebuild` / `prune` / `audit fix`, all of which mutate the tree the symlink points at. The tables are per manager, so `bun upgrade`, which upgrades the Bun binary, stays allowed while `pnpm upgrade` does not. Escape hatch `WEBJS_NO_WORKTREE_INSTALL_GATE=1`.
166
166
167
-
It judges a COMMAND, never a token. The command is split on `&&`, `||`, `;`, `|`, `(` and `)`, and each segment is judged only by what it STARTS with, after leading env assignments and wrappers like `sudo` are stripped. Matching the manager token anywhere in the line is the obvious shortcut and it is badly wrong: it blocks `git commit -m "fix: npm install ..."`, `grep -rn "npm ci" AGENTS.md` and `git log --grep "npm install"`. A linked worktree is the mandated working state here, so that fires on ordinary commands constantly, and a gate that cries wolf is a gate someone turns off. `npm test`, `npm run <script>`, `npx ...`, `npm init` and `yarn test` all pass, and so does a GLOBAL install (`-g` / `--global`), which writes to the npm prefix rather than through the link and is this repo's documented post-release step.
167
+
It judges a COMMAND, never a token, in four stages. Quoted spans are neutralised FIRST, keeping their content but stripping the separators inside them, and heredoc bodies are dropped. The remainder is split on `&&`, `||`, `;`, `|`, `(`, `)` and newlines. Each segment is then judged by its FIRST token, after leading env assignments and wrappers like `sudo` are stripped. Only inside a manager-led segment are the remaining tokens scanned, for the first one recognised as either an install verb or a known safe verb, so a flag sitting before the verb does not hide it. Matching the manager token anywhere in the line is the obvious shortcut and it is badly wrong: it blocks `git commit -m "fix: npm install ..."`, `grep -rn "npm ci" AGENTS.md` and `git log --grep "npm install"`. A linked worktree is the mandated working state here, so that fires on ordinary commands constantly, and a gate that cries wolf is a gate someone turns off. `npm test`, `npm run <script>`, `npx ...`, `npm init` and `yarn test` all pass, and so does a GLOBAL install (`-g` / `--global`), which writes to the npm prefix rather than through the link and is this repo's documented post-release step.
168
168
-**Report.** The root `preinstall` runs `scripts/warn-worktree-install.mjs`, which ALWAYS exits 0 and returns immediately unless `.git` is a FILE, so a normal clone and CI never see it. It names whichever of the three states it landed in and prints the repair.
169
169
-**Repoint on teardown.**`.claude/hooks/cleanup-merged-worktree.sh` repoints any `<primary>/node_modules/@webjsdev/*` link targeting a worktree it is about to remove, before removing it.
170
170
-**Repair on demand.**`npm run worktree:link` repairs the primary's `@webjsdev/*` scope: a dangling link, a link into a live foreign checkout, and an absolute in-primary link all become the relative form, and a DANGLING `.name-HASH` npm staging entry is dropped. A LIVE staging entry is left strictly alone. `npm run check:worktree-links` reports without writing and exits non-zero when there is work. Escape hatch `WEBJS_NO_WORKTREE_REPAIR=1` suppresses the repair WRITE only, so `--check` ignores it and still inspects; the `defaultPrimary()` test needs the hatch for the same reason it needs `WEBJS_NO_WORKTREE_SEED=1`: the repair pass sits ABOVE the primary-checkout guard by design, so it runs in both positions and would otherwise rewrite the real checkout during `npm test`.
0 commit comments