I was midway through a client site refresh when I noticed I had typed the same line three times. "Please don't add explanatory comments to the code." It worked the first time. It worked the second time. Four exchanges later, once we had moved to a different component, a line came back that simply restated the code it sat above.
For a while I assumed the wording was too soft. I added emphasis, then a reason, then both. That went nowhere. The instruction wasn't weak — it was sitting somewhere that never gets read again.
There are layers to where an instruction can live, and each layer is re-read at a completely different rate. Preferences belong near the top; rules you can't afford to lose belong further down. Since I started sorting instructions that way, the days of typing the same sentence three times have mostly gone.
An instruction drifts because its location is never re-read
There are roughly four places you can put an instruction. The higher ones are cheap to write; the lower ones survive repetition.
| Layer | Where it lives | When it gets re-read |
|---|---|---|
| 1. Chat | A message in the conversation | The next few turns only. Summaries and topic shifts dilute it |
| 2. Style / custom instructions | Custom styles, project custom instructions, Claude Code output styles | Across conversations, though it fades as the subject drifts |
| 3. Project settings file | CLAUDE.md, project-level settings | At the start of every session — if the path and key names are right |
| 4. Runtime guardrail | Hooks, verification scripts | Every time a tool runs, for instructions you can actually test |
The same frustration has been piling up in a Claude Code issue thread. What's being argued there is phrasing — how to say it so it sticks. I couldn't find anyone comparing the layers side by side, so I ran the comparison on my own machine.
I put the same sentence in all four places and watched ten exchanges
The setup was deliberately boring. One instruction: don't add explanatory comments to the code. One task: ten small UI fixes on the client site, one after another. Each round, I opened a file, asked for the edit, and noted the exchange where a comment line reappeared.
| Layer | Drifted back at | How it broke |
|---|---|---|
| 1. Said once at the start of the chat | Exchange 3–4 | Right after we switched files |
| 2. Written into a style | Exchange 7 onward | Survives new conversations, thins out inside a long implementation |
| 3. Written into the settings file | Didn't drift | But I had no way to confirm that from inside the session |
| 4. Enforced by a hook | Didn't drift | Only accepts instructions you can evaluate mechanically |
Layer 3 surprised me, and not in the direction I expected. It held — but I had no way to verify that it was holding, and that gap is the real difference between 3 and 4. A settings file with one mistyped key is ignored in total silence. I wrote about that particular silence in A One-Letter Typo in settings.json Is Ignored Without a Single Warning.
Preferences up, rules down
I sort instructions with two questions. Does breaking it cause real cleanup? And can a machine tell whether it was broken?
- Preferences — tone, paragraph length, emoji, how much explanation — live comfortably in layers 1 and 2. If they drift, you restate them, and nobody is measuring them precisely anyway.
- Rules — files that must not be touched, whether commits are allowed, naming conventions, proper nouns that must not be translated — move down to layers 3 and 4. When a rule breaks, someone has to undo the damage, and restating it doesn't get you there.
As an indie developer I hit the same wall while expanding the store listings for my wallpaper apps into more languages. "Don't translate the app name or the feature names" holds in chat for a while, and then somewhere around the third or fourth language it quietly stops holding. Now the do-not-translate terms live in a file that gets read every run, and a final check confirms the proper nouns survived intact. It isn't that I stopped asking in chat — it's that I started asking the right layer.
If a rule must not break, stop explaining it and start blocking it
Here's the layer-4 version. It runs right after an edit and looks only at the file that was just written. This goes in settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/no-echo-comments.sh"
}
]
}
]
}
}And the script it calls. The hook receives JSON on stdin, which is where the edited file path comes from:
#!/usr/bin/env bash
# PostToolUse hook: look at exactly one file, the one just edited
INPUT=$(cat)
FILE=$(printf '%s' "$INPUT" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("file_path",""))')
# Narrow the scope first. Scanning config files and Markdown invites false positives
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx) ;;
*) exit 0 ;;
esac
# Catch comments that only restate the line below them
HITS=$(grep -nE '^[[:space:]]*//[[:space:]]*(updates?|sets?|returns?|gets?|initializes?) the .+$' "$FILE")
# Silence on success
[ -z "$HITS" ] && exit 0
{
echo "Comments that only restate the code are still here. Remove them before moving on:"
echo "$HITS"
} >&2
exit 2What you should see:
$ # immediately after Claude runs an Edit
Comments that only restate the code are still here. Remove them before moving on:
42: // updates the stateThree things matter in that script. First, exit 2 is the signal to try again, and whatever you write to stderr goes back to Claude as the reason. Second, narrow the file types before you scan anything; a hook that inspects every file will stall on comments in config files that were never the problem. Third, say nothing when the check passes — I've written about what a chatty-on-success hook costs you in Don't let your verification script's full output flow back through a hook.
If you're not writing code and hooks aren't part of your setup, stopping at layer 3 is fine. Just make the sentence you put there testable. "Be concise" can't be checked by anyone, including you. "Keep sentences under 25 words" can.
Three things to check when it still drifts
- Confirm the file is actually being read. Plenty of instructions aren't losing an argument; they're never arriving. Check for evidence that the file loaded before you rewrite its contents.
- Ask whether the instruction is testable. "Be thoughtful" won't survive at any layer, because nothing can confirm it held. Trade it for something countable.
- Look for two rules in the same layer pulling opposite ways. Settings files grow, and a line from six months ago can quietly contradict today's. On the related question of how context thins out over a long conversation, I wrote Picking Up Where You Left Off: When to Lean on Claude's Memory and When to Ask It to Search.
One thing to try today: pick a single instruction you've now restated three times in chat, and move it down exactly one layer. The fact that you typed it three times is the signal that it's sitting in the wrong place.
I still get this wrong in the other direction — pushing something down to layer 4 that layer 2 handled fine, then spending an afternoon on false positives. Finding the layer where an instruction settles seems to be part of the work rather than a detour from it.