chore: .gitignore covers a symlinked node_modules, not only the directory form (#15763) - #15821
Merged
baozhoutao merged 1 commit intoSep 5, 2026
Merged
Conversation
…tory form (#15763) `.gitignore:2` is `node_modules/` — the trailing slash makes it match a DIRECTORY only. In a linked worktree whose `node_modules` is a symlink to a shared install, the rule does not match: `git check-ignore -v node_modules` exits 1 and `git status --short` prints `?? node_modules`. Add the slashless form beside it (the existing line is untouched). This repo tracks `node_modules` in no shape, so covering both forms costs nothing, and it closes the `git add -A` hazard recorded twice before (#10781, #12583): an untracked, non-ignored path at the repo root is one careless `git add -A` away from committing a symlink to an absolute container path. Measured effect it also removes: `dispatch-gates.mjs --changed` counts that untracked path as part of the change set, and even derives a gate family from it via `node_modules/@objectstack/spec/CHANGELOG.md`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
This was referenced Sep 5, 2026
baozhoutao
marked this pull request as ready for review
September 5, 2026 09:38
baozhoutao
enabled auto-merge
September 5, 2026 09:38
Contributor
Author
|
Flipped ready + auto-merge enabled (09:38Z, Generated by Claude Code |
baozhoutao
deleted the
claude/issue-15763-gitignore-node-modules-symlink
branch
September 5, 2026 10:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15763
.gitignore:2isnode_modules/. The trailing slash makes the pattern match a directory only. In a linked worktree whosenode_modulesis a symlink to a shared install, git does not see a directory, so the rule does not match and the path is left untracked-and-not-ignored at the repo root.This adds the slashless form beside it. The existing
node_modules/line is untouched — the diff is the new rule plus its one-line comment, with no modification and no deletion. This repo tracksnode_modulesin no shape, so covering both forms costs nothing.Why it is worth a rule rather than a recipe fix: repair 2 (the dispatch recipe returning to AGENTS.md's
pnpm installform) treats how the symlink gets created; this treats the consequence. Even with every seat installing properly, one hand-made symlink leaves the hazard back. That hazard is thegit add -Aclass this repo has recorded twice already (#10781, #12583, both closed): an untracked, non-ignored path at the repo root is one carelessgit add -Aaway from committing a symlink to an absolute container path.Reproduction — measured, not read off the diff
A throwaway linked worktree detached at
origin/main(d8d27768a), withln -s /home/user/objectstack/node_modules node_modules. Exit codes captured before any pipe.BEFORE (base
.gitignore)Positive controls in the same tree, same command, same run — so the exit 1 is a reading and not a broken probe:
The derivation, verbatim:
Stronger than the card recorded. The phantom path does not only widen the count — it matches a check family, through a gate source that lives inside
node_modules. A dev's "gates I ran" list was being seeded with a family derived from a path that can never appear in any PR.AFTER (this branch's
.gitignore, same worktree, same symlink)Controls still answer, at their shifted line numbers — itself corroboration that the file really changed under them:
And the derivation counts the real change set only:
The phantom
check:llms-txtfamily is gone.The directory form is still ignored
Symlink replaced with a real
node_modules/directory containing a file:It answers line 4, not line 2, because git's last-matching-pattern wins. Line 2 is therefore now redundant in effect. It is kept deliberately, per the ruling, so this diff modifies nothing that other seats share.
A ruling expectation this run falsified
Triage's hint asked that
git check-ignore -v node_modules/foostop reportingbeyond a symbolic linkafter the fix. It does not, and no ignore rule could make it. Measured on this branch, with the symlink in place:That fatal is git's pathspec traversal refusal — it happens while resolving the path, before ignore rules are ever consulted. The control above proves the mechanism: with a real directory the identical command exits 0. So the
beyond a symbolic linkreading is a property of the symlink, not of.gitignore, and it is untouched by this PR either way.dispatch-gates.mjs --self-testunder a symlinkednode_modules— reported, not changedAsked for as a measurement. It still refuses, on this branch, with the fix in the tree:
Node exits 1 on the uncaught throw; the 128 is the underlying
git check-ignorestatus the message quotes. (The card and the claim both say "exit 128" — that is the inner status, not the script's.)Same mechanism as above:
exposedScratchDirsprobes a path insidenode_modules, and git refuses to resolve a pathspec through a symlink. No.gitignorerule can reach it. Per the ruling, the self-test is not changed here.Verification
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 7628 text file(s) -- 7628 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes)pnpm check:refd-timer-probe5982 source file(s) sweptpnpm check:watch-hint-literal60 declaration(s) across 4 rostered name(s)node scripts/check-closing-keyword-parity.mjsPREREQUISITE NOT MET - the dependency 'yaml' is not installed)node scripts/check-closing-keyword-parity.mjs --self-testyaml)node scripts/check-comment-mask-corpus.mjsERR_MODULE_NOT_FOUND: '@typescript-eslint/parser')pnpm check:driver-memory-censusts-parse: PREREQUISITE NOT MET - the dependency 'typescript' is not installed)The four NOT MEASURED rows are dependency refusals in a worktree with no
pnpm install, not findings — each gate says so itself and exits before running a check. They are whole-tree declared families named on every card; CI runs them with deps installed. Named rather than dropped.Also run:
node scripts/pm/check-governed-merges.mjs --test .gitignore→ EXIT=0,NOT governed - ordinary queue landing applies to a PR with exactly this file list. Re-derived on the final one-file list, not recalled.git merge-tree --write-tree origin/main BRANCH→ EXIT=0 (clean) against a freshly fetchedorigin/main54bb2f125.GET /pulls/N/files— 0 touch.gitignore. fix(docs): stamp the release index's "current series" at version time (#15332) #15392 (7 files) and fix(release): retry the post-publish npm check, and verify all 69 packages instead of 1 #15331 (3 files) are both clear.grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' .gitignore→ exit 1, no hits.Changeset
None,
skip-changeset. Root.gitignorepublishes nothing from any released package, which is exactly the carve-out AGENTS.md states for that label:Fleet note
.gitignoreis shared by every seat. The change is additive — it removes no coverage, and every path ignored before is still ignored.🤖 Generated with Claude Code
https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Generated by Claude Code