Mihai Dinculescu had a permanently dirty working tree, on purpose. His scratch files — markdown notes, agent plans, that kind of thing — were untracked and stayed untracked, because the only way he knew to ignore a file was .gitignore, and .gitignore gets committed. Pushing the silly filename he picked for his private notes into a shared repository felt unclean, so he did nothing and the files piled up.

Then a coding agent asked, unprompted, whether it should add them to .git/info/exclude. His reaction was the useful part of the post: what is that?

Git reads ignore patterns from three places, all with the same syntax

  • .gitignore — in any directory, affects everyone who clones the repo, committed.
  • .git/info/exclude — affects only this clone, never committed, lives inside .git.
  • core.excludesFile — every repo on this machine, default ~/.config/git/ignore, not committed.

How the split should be used

  • .gitignore: build output and local environments — target/, __pycache__/, .venv/, .env. Things every clone needs to ignore.
  • .git/info/exclude: your own scratch files that the project has no business knowing about. It cannot be committed even by accident, and nobody else ever sees it.
  • Global excludes: editor and OS clutter — .idea/, .vscode/, Thumbs.db, .DS_Store. Which has no business appearing in most project .gitignore files, and yet it does.

The mechanics

echo 'plans/' >> .git/info/exclude
mkdir -p ~/.config/git && echo '.DS_Store' >> ~/.config/git/ignore
  • Patterns are plain text and match relative to the repository root, exactly as in a top-level .gitignore.
  • Git reads ~/.config/git/ignore automatically if it exists; git config --global core.excludesFile <path> points it somewhere else.
  • In a linked worktree .git is a file, not a directory, so use "$(git rev-parse --git-common-dir)/info/exclude", which resolves correctly from the main repo or any worktree.
  • Ignore rules never apply to files that are already tracked — git status only goes quiet for files git has never seen.

One caveat he flags for anyone whose scratch files are agent plans: Claude Code leaves ignored files out of its @ autocomplete, so they vanish from the picker. "respectGitignore": false in settings.json brings them back; it only affects autocomplete, not what the tool can read.

The line worth keeping is his own explanation of why the gap existed: git is the tool most of us learned by accident, some of us more fortunately than others. Three files, one syntax, and two decades of collective not reading the manual.