[BUG] Claude Code writes ignore rules into .git/info/exclude and re-injects them after removal — trips repo commit gates at random, stranding finished work uncommitted
Summary
Claude Code writes ignore patterns into .git/info/exclude — the one git-config surface that is not committed, not shareable, and invisible to code review — and re-injects them automatically after they are removed, observed within ~20 minutes.
On this repo the injected patterns are redundant: every one of them already exists in the versioned .gitignore. So the write adds no ignore behavior. What it does add is a repo-policy violation that fails a pre-commit gate, at random, across every worktree.
What gets written
Ten **/.claude/* patterns, tagged with a marker comment:
# claude-code-runtime
**/.claude/<...>
... (10 total)
Removed 2026-08-07. Re-injected by the harness within ~20 minutes. Removed again. The marker comment makes the author unambiguous — this is not a person or a script in the repo.
Why this breaks things
This repository has a standing rule, enforced by a check that runs in the commit path:
Worktree-copy exclusions belong visibly in versioned.gitignore/.rgignore, never.git/info/exclude.
The audit fails on any non-comment line in that file:
hidden = [line.strip() for line in exclude.read_text().splitlines()
if line.strip() and not line.lstrip().startswith("#")]
if hidden:
errors.append(".git/info/exclude contains local-only ignore policy: ...")
So the sequence is:
- Repo is clean; audit PASSES; commits work.
- The harness re-injects the patterns at some unpredictable moment.
- The audit now FAILS. Commits are blocked repo-wide.
- An agent that just finished real work cannot commit it. Its changes strand in a dirty working tree.
- Someone eventually notices, strips the file, and the cycle restarts.
Step 4 is the expensive one. Work that is finished but uncommitted looks, to every later reader, exactly like work that was never done. On this machine a single uncommitted file sat abandoned for 30 hours and silently blocked an entire class of scheduled regeneration jobs, because a downstream guard refuses to run while its source file is dirty. That is one observed instance of a pattern that has been recurring for months.
Why the location is the aggravating factor
.git/info/exclude is:
- not committed — so it cannot be reviewed, diffed, or shared
- not visible to any tool that inspects
.gitignore - per-clone — so behavior differs between machines and worktrees with no way to tell
It is effectively a fourth, invisible git-configuration surface alongside .gitignore, local config, and per-worktree config. Configuration scattered across surfaces that no single view shows is already a documented review-blindness problem in this repo (see #84161, where an ignore rule made the built-in Grep return "no files found" for code that existed, and three independent review passes agreed it was absent).
Of the available surfaces, the harness picked the only one a reviewer cannot see.
Reproduction
- Ensure
.git/info/excludecontains only comments. - Confirm any check that forbids local-only ignore policy passes.
- Use Claude Code in the repo normally.
- Re-read
.git/info/exclude— the# claude-code-runtimeblock returns.
Observed twice on 2026-08-07, first recurrence within ~20 minutes.
What would resolve this
- Do not write to
.git/info/excludeat all. If the harness needs paths ignored,.gitignoreis the reviewable, shareable, committed surface — and in this case the patterns were already there, so the write was a no-op that only caused harm. - If a local-only surface is genuinely required, make it opt-in and documented — a setting such as
harnessLocalExcludes: false, and a line in the docs saying the harness writes there. Right now the only way to discover this is to find the marker comment after something breaks. - Never re-assert after removal. A user deleting a harness-written rule is an explicit signal. Silently restoring it converts a one-time surprise into an unfixable recurring one.
- At minimum, log it. A single line — "wrote N patterns to .git/info/exclude" — would have turned months of "why do commits randomly fail" into a five-second answer.
Environment
- Claude Code 2.1.223 → 2.1.224, Windows 11, git 2.47.1
- Repo with ~15-20 concurrent worktrees; patterns observed re-injected into the main tree
- All injected patterns verified already present in the committed
.gitignore