Found while evaluating 0.24.0-rc.3 in an adopter project.
Summary
repin_npm() walks the consumer tree with:
find "$PROJECT" -name package.json -not -path '*/node_modules/*' -not -path '*/.git/*'
-not -path '*/.git/*' prunes paths inside a .git directory. But a linked git worktree's .git is a file (containing gitdir: …), so nothing under a nested worktree is pruned. link therefore repins tracked manifests belonging to a different checkout on a different branch.
Observed
The adopter keeps agent worktrees under .claude/worktrees/<name>/. Running:
prerelease-link.sh link --version 0.24.0-rc.3
reported:
repinned 7 @metaobjectsdev/* dependencies to 0.24.0-rc.3 across 4 manifest(s)
Only 2 of those manifests belonged to the project being linked. The other 2 were inside a nested worktree checked out on an unrelated branch that had its own uncommitted work in progress.
Why this is worse than a stray edit
That nested worktree was deliberately pinned to an older MetaObjects (0.19.0). unlink_all() calls the same walk (repin_npm "$TO"), and --to defaults to the current public latest. So the documented recovery:
prerelease-link.sh unlink --to 0.23.2
would have rewritten those manifests to 0.23.2 — a version they were never on. link → unlink is not round-trip safe across a nested worktree: it silently relocates an unrelated branch's dependency pin, and nothing records that it happened.
The .git/info/exclude protection does not help here: these are tracked files, and they belong to a different worktree.
Suggested fix
Constrain the walk to the worktree being linked. Roughly in order of robustness:
- Prune any directory other than
$PROJECT that is itself a worktree root — i.e. contains a .git entry of either kind, file or directory.
- Ask git directly:
git -C "$PROJECT" worktree list --porcelain, prune every returned path except $PROJECT.
- Enumerate candidates with
git -C "$PROJECT" ls-files -- '*package.json', so only files tracked by this worktree are considered.
Option 1 additionally covers nested submodules and vendored unrelated repos, which the current find has the same blind spot for.
The nuget / python / maven paths do their own walking and globbing, so they are likely to share this and are worth checking together.
Found while evaluating
0.24.0-rc.3in an adopter project.Summary
repin_npm()walks the consumer tree with:-not -path '*/.git/*'prunes paths inside a.gitdirectory. But a linked git worktree's.gitis a file (containinggitdir: …), so nothing under a nested worktree is pruned.linktherefore repins tracked manifests belonging to a different checkout on a different branch.Observed
The adopter keeps agent worktrees under
.claude/worktrees/<name>/. Running:reported:
Only 2 of those manifests belonged to the project being linked. The other 2 were inside a nested worktree checked out on an unrelated branch that had its own uncommitted work in progress.
Why this is worse than a stray edit
That nested worktree was deliberately pinned to an older MetaObjects (
0.19.0).unlink_all()calls the same walk (repin_npm "$TO"), and--todefaults to the current publiclatest. So the documented recovery:would have rewritten those manifests to
0.23.2— a version they were never on.link→unlinkis not round-trip safe across a nested worktree: it silently relocates an unrelated branch's dependency pin, and nothing records that it happened.The
.git/info/excludeprotection does not help here: these are tracked files, and they belong to a different worktree.Suggested fix
Constrain the walk to the worktree being linked. Roughly in order of robustness:
$PROJECTthat is itself a worktree root — i.e. contains a.gitentry of either kind, file or directory.git -C "$PROJECT" worktree list --porcelain, prune every returned path except$PROJECT.git -C "$PROJECT" ls-files -- '*package.json', so only files tracked by this worktree are considered.Option 1 additionally covers nested submodules and vendored unrelated repos, which the current
findhas the same blind spot for.The
nuget/python/mavenpaths do their own walking and globbing, so they are likely to share this and are worth checking together.