Found while evaluating the 0.24.0-rc.* line in an adopter project — the same evaluation that produced #323, #324, #325 and #330.
link warns you when it writes to a file that is tracked by definition and therefore cannot be hidden in .git/info/exclude:
! pyproject.toml is tracked by definition — the managed block is committable; 'unlink' removes it
! pom.xml is tracked by definition — the managed block is committable; 'unlink' removes it
package.json is equally tracked by definition, is repinned by the same run, and gets no such warning. repin_npm finishes with a bare count:
repinned 12 @metaobjectsdev/* dependencies to <version> across 3 manifest(s)
Verified on current main (f5913cedc): repin_npm ends at say "repinned $total …" with no tracked check, while the pyproject.toml and pom.xml warnings sit at lines 466 and 534.
Why this is worth a warning and not just tidiness
It leaked to a shared branch twice, in one repository, in two different manifests, from two unrelated commits. Both times the mechanism was identical: link repinned a tracked package.json during pre-release testing, and the repin rode along in a commit about something else. Six version-string lines in a large diff read as noise, and neither leak was caught in review.
The consequences differed only in how long they hid:
- one surfaced when an install failed — the manifest named a pre-release iteration that had since been superseded and removed from the private registry, so it resolved from nowhere: not public, not private.
pnpm install --frozen-lockfile (true by default in CI) failed with six specifier mismatches, because the lockfile had never been regenerated and still named the public version.
- the other sat on the default branch for weeks, in a second manifest, undetected. It was found only by running
detect-prerelease-pins.sh by hand — after the first had already been fixed, by someone who assumed the first was the only one.
That second point is the argument. The repo had four other artifacts still naming the public version (the lockfile, a sibling manifest, a CI workflow pin, and the committed codegen), and none of them made the divergence visible. A per-file warning naming the manifest is what a reviewer would have had to skim past deliberately.
The global warning does not cover this
link already ends with:
! this project now depends on artifacts that exist ONLY on the private registry.
! run 'unlink' before pushing anything from it.
That is a statement about the project. It is printed once, before the developer starts the work they actually linked in order to do, and it names no file. The pyproject.toml/pom.xml warnings are different in kind — they name the specific artifact that will end up in git status, which is the thing a reviewer sees.
Suggested fix
Have repin_npm warn per tracked manifest it rewrites, in the same register as the other two:
! <path>/package.json is TRACKED — the repin is committable; 'unlink' reverts it
One line per manifest is noisier than a count, but that is the point: the count is what made the leak invisible. The path is what makes it reviewable.
Adjacent, possibly worth folding in
detect-prerelease-pins.sh found both leaks instantly, once run — including one that had been on the default branch for weeks. But it is installed by link and described as something to commit and run in CI, which is guidance an adopter reads while linking and acts on later, if at all. Wiring it in at link time (or making link refuse to finish until it is gated) would close the loop the two warnings above only narrow.
Found while evaluating the
0.24.0-rc.*line in an adopter project — the same evaluation that produced #323, #324, #325 and #330.linkwarns you when it writes to a file that is tracked by definition and therefore cannot be hidden in.git/info/exclude:package.jsonis equally tracked by definition, is repinned by the same run, and gets no such warning.repin_npmfinishes with a bare count:Verified on current
main(f5913cedc):repin_npmends atsay "repinned $total …"with notrackedcheck, while thepyproject.tomlandpom.xmlwarnings sit at lines 466 and 534.Why this is worth a warning and not just tidiness
It leaked to a shared branch twice, in one repository, in two different manifests, from two unrelated commits. Both times the mechanism was identical:
linkrepinned a trackedpackage.jsonduring pre-release testing, and the repin rode along in a commit about something else. Six version-string lines in a large diff read as noise, and neither leak was caught in review.The consequences differed only in how long they hid:
pnpm install --frozen-lockfile(true by default in CI) failed with six specifier mismatches, because the lockfile had never been regenerated and still named the public version.detect-prerelease-pins.shby hand — after the first had already been fixed, by someone who assumed the first was the only one.That second point is the argument. The repo had four other artifacts still naming the public version (the lockfile, a sibling manifest, a CI workflow pin, and the committed codegen), and none of them made the divergence visible. A per-file warning naming the manifest is what a reviewer would have had to skim past deliberately.
The global warning does not cover this
linkalready ends with:That is a statement about the project. It is printed once, before the developer starts the work they actually linked in order to do, and it names no file. The
pyproject.toml/pom.xmlwarnings are different in kind — they name the specific artifact that will end up ingit status, which is the thing a reviewer sees.Suggested fix
Have
repin_npmwarn per tracked manifest it rewrites, in the same register as the other two:One line per manifest is noisier than a count, but that is the point: the count is what made the leak invisible. The path is what makes it reviewable.
Adjacent, possibly worth folding in
detect-prerelease-pins.shfound both leaks instantly, once run — including one that had been on the default branch for weeks. But it is installed bylinkand described as something to commit and run in CI, which is guidance an adopter reads while linking and acts on later, if at all. Wiring it in at link time (or makinglinkrefuse to finish until it is gated) would close the loop the two warnings above only narrow.