--- name: gitignore-trailing-slash-symlink-contamination description: "Trailing-slash gitignore rules don't match symlinks, so worktree dependency symlinks get tracked and pushed as 1-line blobs; same-name rules silently match worktree dirs, and .deploy-stale pollutes. Three fixes with tests." --- Gitignore semantics trap with three real-world consequences (serverAppConfigs, 2026-10-19): ## The rule `.gitignore` patterns ending in `/` match directories only. Worktree-relative: `**/node_modules/` is `**/node_modules/` → a symlink named `node_modules` does NOT match, so it is "untracked" no more — it gets added, committed, and pushed as a 1-line blob containing its target path. ## Consequence 1: worktree symlink contamination - `git add -A` then commit then push in a worktree whose root holds `node_modules -> ...` (the worktree convention symlink) commits it. - The repo then holds `package/frontend/node_modules` as a text file containing the absolute path on the author's box. - The receiving worktree gets that 1-line file instead of the symlink → "module not found" and "why is this a 30-byte file?". - Detection: `git ls-tree -r origin/main | awk '$1=="120000"'` (mode 120000 = symlink) or `git ls-files -s | grep 120000`. - Fix: ignore rules WITHOUT trailing slash (`**/node_modules`, `**/.venv`) so symlinks match too. ## Consequence 2: same-name worktree rules silently matching worktree dirs A root rule like `wt-*` (meant to ignore local worktree checkouts) matches every path whose last segment starts with `wt-` — so legitimate content under such a directory goes untracked. `git status --ignored` is the only visible trace; detection: `git check-ignore -v ` prints the owning rule + line number. (Repo-specific: `dank/command-center`'s `.gitignore` carries a "per-worktree memory buckets" block ignoring `agent/memories/sharpshooter/wt-*/` and `agent/memories/summaries/wt-*/` — intentional there.) ## Consequence 3: `.deploy-stale` `deploy.sh` renames a stale tree to `.deploy-stale` before recreating it. Without an ignore rule those directories land in `git status` as untracked and get swept into `git add -A` commits; they accumulate on every server. ## Fixes with tests ```gitignore **/node_modules **/.venv .deploy-stale/ ``` Black-box test against the real rules, not a fixture: `git check-ignore -v ` must name the rule. `git check-ignore` takes hypothetical paths — nothing needs to exist on disk: ```bash git check-ignore -v natives/x.wasm dotfiles/x/.config/noctalia/Noctalia.json ``` Two ignore surfaces must be kept in sync (deploy exclusions vs ignore rules): if a deployer excludes `*/Noctalia*` but the ignore list has only the lowercase class, generated files become tracked-but-never-deployed — test both halves with `git check-ignore`. ## Recovery (if contamination already pushed) 1. `git rm --cached ` on the symlink blob, commit, push. 2. Fix the ignore rule first (else the next `git add -A` re-adds it). 3. Untracked ≠ deleted from history: the blob stays in every old commit. Only a rewrite removes it; that is the operator's call, not a cleanup run alone.