Files
command-center/agent/managed-skills/gitignore-trailing-slash-symlink-contamination/SKILL.md
T

3.1 KiB

name, description
name description
gitignore-trailing-slash-symlink-contamination 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.

  • 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 <path> 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 <path>.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

**/node_modules
**/.venv
.deploy-stale/

Black-box test against the real rules, not a fixture: git check-ignore -v <symlink-target> must name the rule. git check-ignore takes hypothetical paths — nothing needs to exist on disk:

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 <path> 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.