--- name: gitignore-trailing-slash-symlink-contamination description: "Trailing-slash gitignore rules don't match symlinks, so worktree dependency symlinks get committed; detect with git ls-tree mode 120000, fix with git rm --cached." --- A `.gitignore` entry written with a **trailing slash matches directories only**. A symlink of the same name is not a directory, so it slips past the rule, and `git add -A` in a `git worktree` checkout silently **tracks** it. Cost measured 2026-10-19: a machine-specific symlink (`node_modules -> /home/blank/Projects/dankvoice/node_modules`) landed in `main` with a merged PR. Why worktrees are the trigger: a worktree has no `node_modules` (or other installed-deps dir) of its own, so the cheap way to make build/test work there is a symlink into the primary checkout. With `node_modules/` ignored, nothing complains. ## Detect ```bash git ls-tree HEAD -- node_modules # mode 120000 == tracked symlink git ls-files -s | awk '$1==120000' # every tracked symlink in the index ``` ## Fix (branch, never on main directly) ```bash git rm -q --cached node_modules # untrack; leaves the file on disk ``` plus relax the ignore rule to the slash-free form, with the reason inline so nobody re-adds the slash: ``` # Trailing slash would only match directories: a node_modules symlink (the # shape `git worktree` checkouts end up with) slips past it and gets tracked. node_modules ``` Then `git check-ignore -v node_modules` must print the rule, and the suite runs after (the symlink still resolves, it is just ignored). ## Prevent In a fresh worktree, create the deps symlink **after** confirming the ignore rule has no trailing slash: ```bash git check-ignore -v node_modules || sed -i 's|^node_modules/$|node_modules|' .gitignore ``` Same class for any ignored-by-convention dir that a worktree might provide as a link: `node_modules`, `vendor`, `.venv`, `target`, `dist` when built artifacts are ignored.