[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

Status Open
Reported on v2.1.223
Maintainer reply None cached
Activity 0 comments · opened Aug 8, 2026

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:

  1. Repo is clean; audit PASSES; commits work.
  2. The harness re-injects the patterns at some unpredictable moment.
  3. The audit now FAILS. Commits are blocked repo-wide.
  4. An agent that just finished real work cannot commit it. Its changes strand in a dirty working tree.
  5. 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

  1. Ensure .git/info/exclude contains only comments.
  2. Confirm any check that forbids local-only ignore policy passes.
  3. Use Claude Code in the repo normally.
  4. Re-read .git/info/exclude — the # claude-code-runtime block returns.

Observed twice on 2026-08-07, first recurrence within ~20 minutes.

What would resolve this

  1. Do not write to .git/info/exclude at all. If the harness needs paths ignored, .gitignore is 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.
  2. 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.
  3. 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.
  4. 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

View original on GitHub ↗