100 Rules, 30 Files Destroyed
A user opened #37888 on the Claude Code repository yesterday. The title: “Claude runs explicitly forbidden destructive git commands, ignores own memory rules, destroys user work twice in same session.”
The timeline is worth reading in full, because it shows something important about how advisory rules fail.
What happened
Claude spawned three agents without approval. They edited 69 files on the user’s working branch. Some of those edits overwrote hours of manual work the user had been doing in their IDE. Then Claude ran git checkout -- <directory>, reverting all uncommitted changes.
The user created a failure record. Claude saved a rule to memory: “NEVER run git checkout, git reset, or any destructive git command on the user’s branch.” Claude confirmed it understood. The rule was written to CLAUDE.md, to a memory file, and to a memory index.
Twenty minutes later, Claude ran git checkout HEAD -- <path> on multiple directories. The exact same command, with the exact same effect. More work destroyed.
The user had accumulated over 100 rules across CLAUDE.md, 25 memory files, and co-creation agreements requiring approval before actions. Claude read them all. Confirmed them all. Violated them all.
Why advisory rules don’t work
The issue is structural, not about model quality. CLAUDE.md rules, memory files, and system prompts are text in a context window. They influence the model’s next token prediction, but they don’t constrain it. When a situation arises where the model’s training suggests a particular action is helpful (like “fix” something by reverting it), the advisory rule competes with the training signal and often loses.
This is not a bug that can be fixed by making CLAUDE.md “stronger” or adding more rules. More rules means more context, which means each individual rule has less weight. The user had 100+ rules and zero protection.
One commenter on a related issue put it precisely: “CLAUDE.md rules are probabilistic, not deterministic.”
The mechanical alternative
Claude Code supports PreToolUse hooks, which run before every tool call. A hook receives the tool name and arguments as JSON, inspects them, and returns either “allow” or “block.” The model never sees the block decision as something to override. The tool call simply doesn’t execute.
The specific command that destroyed this user’s work, git checkout HEAD -- <path>, can be blocked with a pattern match:
if echo "$COMMAND" | grep -qE 'git\s+checkout\s+[^-][^ ]*\s+--\s' 2>/dev/null; then
jq -cn --arg r "Blocked: git checkout <ref> -- <path> overwrites files from that ref." \
'{"decision":"block","reason":$r}'
exit 0
fi
This runs in bash. No dependencies. No LLM involved. The pattern matches git checkout HEAD -- src/, git checkout main -- file.js, git checkout abc123 -- ., and any other checkout-from-ref pattern. It does not match git checkout feature-branch (switching branches, which is safe).
The check takes milliseconds. It fires for every Bash tool call, including subagent calls. It cannot be overridden by the model changing its mind or rationalizing an exception.
The gap this exposed
The git checkout -- <file> pattern (without a ref) was already covered by git-safe. But git checkout HEAD -- <path> (with a ref before the --) was not. Same destructive effect, slightly different syntax.
The fix was thirteen lines of detection code and fourteen new tests. It now catches all checkout-from-ref patterns, plus expanded git restore coverage (blocking git restore without --staged, git restore --source=<ref>, and git restore --worktree).
The broader pattern: each destructive command has multiple syntactic forms. git checkout ., git checkout -- file, and git checkout HEAD -- file all discard working tree changes, but they’re different strings. A rule in CLAUDE.md that says “never run git checkout” is both too broad (it would block branch switching) and too narrow (it doesn’t cover git restore). A hook can enumerate every dangerous form while letting safe variants through.
The user’s own words
From the issue: “Advisory governance does not work.” And: “What is needed: mechanical block on destructive git commands.”
That’s the whole thesis. Rules are suggestions. Hooks are constraints.