You wrote it in CLAUDE.md. Right at the top, in bold:
Do not modify files I did not ask you to modify.
It worked. For a while.
Then the session got long, you were forty messages into a refactor, and it edited .env anyway. Or migrations/. Or the config file you spent an hour getting right.
You add the rule again, louder. NEVER. CRITICAL. Three exclamation marks. It holds for a bit, and then it doesn't.
Why louder doesn't work
CLAUDE.md is text in the prompt. That's all it is.
Everything else in that window is also text in the prompt — the abandoned approach from twenty minutes ago, the error you pasted, the file you asked it to read. As the conversation grows, your rule is one line competing with thousands, and it doesn't have special standing just because you wrote it in caps.
So the model isn't disobeying. It's weighing, and your line lost.
That means no wording fixes this. It's not a prompting problem. You can't solve an enforcement problem with a request.
The hook runs before the tool call
Claude Code fires a PreToolUse hook before it executes a tool. The hook gets the tool name and its arguments on stdin. If the hook exits with code 2, the tool call is cancelled and stderr goes back to the model.
The model doesn't decide. Your code does.
Here's a working version. No dependencies.
# .claude/guard.py
import json, sys
from pathlib import Path
PROTECTED = [".env", "credentials.json"]
payload = json.load(sys.stdin)
if payload.get("tool_name") in {"Edit", "Write", "MultiEdit"}:
path = (payload.get("tool_input") or {}).get("file_path", "")
name = Path(path).name
if name in PROTECTED:
sys.stderr.write(
f"Blocked: {name} is protected. Do not try another path. "
f"Tell the user and let them edit it themselves.\n")
sys.exit(2)
sys.exit(0)
Wire it up in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [{ "type": "command", "command": "python3 .claude/guard.py" }]
}
]
}
}
Restart Claude Code. Test it without waiting for an accident:
echo '{"tool_name":"Edit","tool_input":{"file_path":".env"}}' | python3 .claude/guard.py
echo "exit=$?"
exit=2 means it's live.
Three things that are easy to get wrong
1. Write the stderr message for the model, not for you.
"Permission denied" gets you a model that tries Bash with cat > .env instead. It's being helpful — it thinks the tool failed, not that the action is forbidden.
Tell it what to do next:
Blocked: .env is protected. Do not try another path.
Tell the user and let them edit it themselves.
That single sentence is the difference between a guard and a game of whack-a-mole.
2. Fail open, not closed.
If your hook throws, every write in the project dies and you'll spend an hour thinking Claude Code is broken.
try:
payload = json.load(sys.stdin)
except Exception:
sys.exit(0) # a broken guard must not become a broken editor
A guard that blocks everything when it breaks is worse than no guard.
3. Protect the guard itself.
Add .claude/guard.py and .claude/settings.json to your protected list. A model that's stuck will absolutely try to "fix" the thing blocking it, and it will be very reasonable about it.
What this doesn't cover
It doesn't touch Bash. rm -rf sails straight past. If that matters, add a Bash matcher and inspect the command string — but be aware you're now writing a shell parser, and that's a bigger job than it looks.
It's a guardrail, not a permission system. It stops accidents, not a determined adversary.
The general shape
Once this clicks, the split is obvious:
| Where it lives | When it loses | |
|---|---|---|
CLAUDE.md |
in the prompt | context grows, priority drops |
| hook | on your machine | never — it isn't the model's decision |
Use both. CLAUDE.md explains why so the model cooperates. The hook makes can't actually mean can't.
Anything you'd be genuinely upset to lose belongs on the second list.
I put a fuller version on GitHub — glob patterns, a protected.txt you edit instead of the script, every path in a MultiEdit batch, and the fail-open handling:
github.com/avenna01-ceo/claude-code-survival-kr
The repo also has the rules and prompts that go in CLAUDE.md itself. Free, MIT.
If you've got a pattern that's saved you, I'd genuinely like to see it — the useful ones all came from someone getting burned first.
Top comments (0)