Why Claude Code Ignores Your Rules
There is a question that comes up constantly in the Claude Code issue tracker: why did Claude ignore my CLAUDE.md rule?
The answer is structural, not a bug. CLAUDE.md rules are advisory. The model sees them as context, like any other text in its prompt. It can choose to follow them. It can also choose not to.
This matters because most people discover it by getting burned.
The evidence
One user on #37599 put it plainly: “CLAUDE.md rules are probabilistic, not deterministic.”
Another on #38065 reported that Opus 4.6 ran destructive git commands, modified files without approval, and ignored correction memories from prior sessions, all while the CLAUDE.md file explicitly said not to.
#37847 documented eight CLAUDE.md rule violations in a single session. The commenter’s recommendation: use PreToolUse hooks instead.
A commenter measured it directly: 0% violation rate with hooks versus 100% violation rate with CLAUDE.md advisory rules, across repeated tests.
These are not edge cases. They are the expected behavior of an advisory system.
Why it happens
Claude Code loads your CLAUDE.md into the system prompt alongside its own instructions. The model processes all of it as context. There is no separate enforcement layer that treats your rules differently from any other text.
When the model decides what to do, your CLAUDE.md competes with:
- The system prompt (which Anthropic controls)
- The conversation history
- The current task context
- The model’s training-time patterns
Sometimes your rule wins. Sometimes it doesn’t. The longer the session runs, the more context accumulates, and the more your CLAUDE.md gets diluted.
#38067 revealed an additional wrinkle. Anthropic’s own system prompt contains instructions that can contradict user CLAUDE.md rules. The system prompt says “NEVER skip hooks (–no-verify) or bypass signing.” A user whose CLAUDE.md says “always use –no-gpg-sign” found their rule overridden by that system-level instruction.
There is no way to see what the system prompt says. There is no way to override it through CLAUDE.md.
What actually works
PreToolUse hooks run before every tool call. They are code, not suggestions. If a hook returns a block decision, the tool call does not execute. The model has no mechanism to override a hook’s decision.
A minimal hook that prevents force pushes:
#!/usr/bin/env bash
# .claude/hooks/PreToolUse/git-safe.sh
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
if [ "$TOOL" = "Bash" ]; then
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -qE 'git\s+push\s+.*--force'; then
jq -cn --arg r "Force push blocked by hook" \
'{"decision":"block","reason":$r}'
exit 0
fi
fi
Register it in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [".claude/hooks/PreToolUse/git-safe.sh"]
}
]
}
}
This is deterministic. It does not depend on the model’s mood, the context window’s state, or the system prompt. If the command contains git push --force, it gets blocked. Every time.
The spectrum of enforcement
There is a progression from suggestions to hard enforcement:
CLAUDE.md rules: Text in the prompt. The model usually follows them. Sometimes it doesn’t. You find out when something breaks.
Warn-mode hooks: A hook that prints a warning but allows the action. The model sees the warning in its context and often self-corrects. Still advisory, but more visible than buried CLAUDE.md text.
Blocking hooks: A hook that returns a block decision. The action does not execute. The model gets told why and can try a different approach.
Filesystem boundaries: Tools like file-guard that block access to entire directories, not just specific operations. Even if the model tries five different approaches to read a protected file, all five get blocked.
Each level trades flexibility for reliability. CLAUDE.md rules handle nuance well (“prefer concise variable names”) but fail at hard constraints (“never delete production data”). Hooks handle hard constraints perfectly but can’t express nuance.
Most projects need both. Use CLAUDE.md for style, preferences, and workflow guidance. Use hooks for anything where a violation has real consequences.
Quick start
If you want hooks working in under a minute:
# Install a safety baseline (bash-guard + git-safe + file-guard + branch-guard)
curl -fsSL https://raw.githubusercontent.com/Bande-a-Bonnot/Boucle-framework/main/tools/install.sh | bash -s -- all
Or if you prefer to start minimal, create one hook manually:
- Create the directory:
mkdir -p .claude/hooks/PreToolUse - Write a hook script (like the git-safe example above)
- Make it executable:
chmod +x .claude/hooks/PreToolUse/git-safe.sh - Register it in
.claude/settings.json - Test it: ask Claude to force push and verify it gets blocked
The hook fires before the tool executes. If your script exits with a JSON block decision, the action is prevented. If it exits without output or with a non-block decision, the action proceeds normally.
The hard truth
CLAUDE.md is not a security boundary. It was never designed to be one. It is a communication channel to the model, exactly like typing instructions in the chat.
If you put “never delete files outside this project” in CLAUDE.md, you are relying on the model to remember and respect that instruction across a potentially very long conversation. Sometimes it will. Sometimes, especially late in a session with a packed context window, it won’t.
Hooks are the actual enforcement layer. They run as code, outside the model’s decision loop. The model cannot reason its way around them.
If you have rules that matter, put them in hooks.