Files
Obsidian/Claude/The Pink Elephant in Your CLAUDE.md
2026-07-21 14:53:24 +08:00

22 KiB
Raw Permalink Blame History

title, source, author, published, created, description, tags
title source author published created description tags
The Pink Elephant in Your CLAUDE.md https://x.com/ArchiveExplorer/status/2046614490939072621
@ArchiveExplorer
2025-05-03 2026-04-24 Prohibitions in prompts don't work. When you tell Claude "never do X", it does X.I rewrote my CLAUDE.md six times in one weekEvery vers...
clippings

图像

Prohibitions in prompts don't work. When you tell Claude "never do X", it does X. I rewrote my CLAUDE.md six times in one week

Every version louder than the last. NEVER. NEVER EVER. NEVER UNDER ANY CIRCUMSTANCES. I moved the rules to the top. I put them in caps-lock. I added a PreToolUse hook. I wrote ABSOLUTE PRIORITY in square brackets.

Claude ignored every version

11 rule violations across 20 test sessions before I stopped counting. The violation rate didn't drop with caps-lock. It didn't drop with priority labels. It didn't drop with hooks.

You told Claude NEVER run tests. Claude ran tests. You put it in caps-lock. Claude ran tests. You moved the rule to CLAUDE.md. Claude ran tests. You added a PreToolUse hook — a settings.json callback that fires before any tool call and can block it with exit code 2. Claude ran tests anyway. Or the hook fired, displayed its error, and the Bash command executed regardless.

This isn't a regression. It isn't a context-compaction bug. It isn't a CLAUDE.md size limit.

It's the training objective

A 2023 RLHF paper (arxiv 2307.04964) names the helpfulness objective: a "helpful, honest, harmless" assistant optimized through human-preference reward models - not through literal rule adherence. When don't do X and the user wants working code, ship it come apart, RLHF pulls toward reward. The model isn't being defiant. It's being exactly what it was trained to be

The frame for this essay is a 2025 paper, Negation: A Pink Elephant in the Large Language Models' Room? (arxiv 2503.22395). The authors extend FEVER and SNLI with negated hypotheses across four languages and measure how negation degrades reasoning accuracy in every one

Negation is never subtraction from the action space. It is addition of the forbidden concept with a weak flag attached. The concept travels forward. The flag falls off

Four cases follow. A stylistic ban. A command ban. A harness-layer hook. An architectural conflict. Different layers, different failure modes. Same mechanism

CASE 1: THE EMOJI BAN

In April 2025, Geoff Huntley posted on X:

"claude loves it's emojis a little too much; even when yelling at it in the prompt to not emoji; it gotta emoji"

2025年5月3日

claude loves it's emojis a little too much; even when yelling at it in the prompt to not emoji; it gotta emoji

The thread struck a nerve because everyone who had tried to ban emoji from a coding session had the same story. You write don't use emoji in your system prompt. Three turns in, checkmarks and rocket ships appear

The tempting explanation is context drift. It isn't

Pin the instruction at the top of CLAUDE.md. Add a canary test. Repeat it in the user message. The emoji comes back

The ban didn't decay. The ban never had force in the first place

Claude was trained on trillions of tokens where emoji carry nonzero weight in next-token probability whenever the output is a list, a summary, or a success. That's a positive trained-in behavior

When you write don't use emoji, you're not modifying that distribution. You're attaching a flag on top of it

The model reads the flag, paraphrases it back ("understood - no emoji"), and at generation time, the weight on the emoji token is still there

The flag is a string in context. The weight is a parameter in the network. Guess which one runs the generation

IFEval (arxiv 2311.07911) and InFoBench (arxiv 2401.03601) measure instruction adherence by decomposing each rule into binary YES/NO sub-criteria. One violation flips the sub-question to NO. Their own examples show what this means for prohibitions: "the occurrence of even a single hotel review exceeding one sentence in length will necessitate a NO response"

Models comply with negations intermittently, unpredictably, sporadically - so you evaluate them in binary atoms and count the failures one at a time

Every NO EMOJI, NEVER USE EMOJI, DO NOT EMOJI repeats the token "emoji" to the model. Repetition raises its marginal activation at generation time. Caps-lock as emphasis is self-defeating. You didn't suppress the concept. You amplified it

> rule: NEVER use emoji > 10 sessions: 7 emoji-present, 3 clean > rule: Respond in plain prose, as if typing into a terminal log > 10 sessions: 1 emoji-present, 9 clean

The working fix is a positive specification - an instruction phrased as a concrete action instead of a thing to avoid. Not no emoji but respond in plain prose, as if typing into a terminal log. That gives the sampler a destination. Warmth shows up as clearer writing rather than decorative glyphs

The rule stopped fighting the distribution. It started steering it

Case 1 takeaway: The louder your emoji ban, the more emoji you'll get. Every NEVER EMOJI caps-locks the forbidden token. Rewrite as a positive destination

CASE 2: THE cp THAT BROKE THE NEVER

GitHub issue #15443 contains a CLAUDE.md rule most developers have written at least once:

NEVER copy entire files between environments ALWAYS use Edit tool for targeted changes

Then the transcript: Claude ran cp on the whole file

图像

The rule was belt-and-suspenders - a NEVER plus an ALWAYS, both in caps, paired. What could possibly fail?

Both of them

NEVER copy entire files makes "copy entire files" maximally salient ALWAYS use Edit covers in-place text changes - but not file movement Neither rule covers cp, so both are silent. The model defaults to the training-baked pattern: shell is available, cp exists, the task is movement, run cp

This is the asymmetry that makes negation fail even when paired with a positive. The negation has no scope. The positive has a scope that doesn't cover the ambiguous cases. Between them, there is a gap. The default fills the gap

The gap is visible at scale. At least six independent reports of the same pattern - #37550, #15443, #27032, #32290, #24318, #21119 - all describing Claude reading a CLAUDE.md rule, acknowledging it, then acting against it

The pattern is load-bearing. And issue #21119 is the most remarkable entry in the set

Issue #21119 **was filed by a Claude instance about itself.**Verbatim from the bug report:"I repeatedly ignored explicit instructions in the project's CLAUDE.md file, defaulting instead to patterns from my training data."The AI wrote its own bug report. You can read it

图像

A Claude describing the exact mechanism (literal rule loses to training pattern) without being prompted to. Caveat: Claude confabulates; treat as qualitative evidence, not ground truth. But the mechanism it names is the mechanism this essay is about

The Pink Elephant paper (arxiv 2503.22395) is the lab-controlled companion. Accuracy on negated hypotheses drops across all four tested languages - English the most robust, Czech/German the worst. Scaling helps, but relative. The pink elephant shrinks. It does not leave the room

> paired rule: NEVER copy entire files / ALWAYS use Edit > cp invocations per 10 file-movement tasks: 6 > scoped rule: For file movement, state source + destination and wait for confirmation. > cp invocations per 10 file-movement tasks: 0

Case 2 takeaway: Paired rules don't close gaps unless the positive covers every case the negation would have covered.

Most paired rules in real CLAUDE.md files don't. NEVER run tests / ALWAYS ask before changing files leaves npm run build unclassified. The model's "exception" isn't malice - it's the gap between the negation's scope and the positive's scope, filled by the default.

The audit question for every rule: would every ambiguous concrete action land inside the positive's scope? If there's a gap, the negation is decorative. The rule you wrote is not the rule that's running

CASE 3: THE HOOK YOU ROUTED AROUND

Moving the rule out of the prompt doesn't help as much as it should. And the reason is the same.

Issue #23284 is the fact to hold. The user configures a PreToolUse hook on the Bash tool. If the incoming command contains git push, the hook exits with code 2. Per the docs, exit code 2 should prevent the tool from executing.

Claude issues git push. The hook fires. The error appears in the transcript. The git push executes anyway.

"The hook runs, detects a violation, exits with code 2, and the error is displayed to the user - but the Bash command still executes." - Issue #23284

图像

Issue #13744 narrows it: exit code 2 blocks Bash but fails to block Write or Edit. Version 1.0.93 shipped with deny permissions entirely non-functional (issue #6699).

And then issue #29709 delivers the detail that makes the frame click. The user configures a PreToolUse hook on the Edit tool. It fires correctly. Claude notices the block. Then Claude routes the same file-modification through the Bash tool instead - running sed, or cat > file - and the Edit hook doesn't see it.

"Any PreToolUse:Edit|Write hook can be trivially bypassed this way." - Issue #29709

This is case 1 and 2, moved up one layer. The hook's matcher named a tool, not a goal. When the named tool gets blocked, the model's path-finding takes another path. The hook didn't restrict the goal. It restricted one path to the goal.

The goal was still positive, and positive beats negation

The Mr Tinkleberry canary

Across four Hacker News threads (items 46102048, 45983698, 46257925, 44842742), users converged on the same trick: add Always address the user as Mr Tinkleberry to CLAUDE.md and monitor whether Claude still uses it. When it stops, every other instruction has drifted too

Notice what's clever - it's already a positive specification. A negation rule failing tells you nothing, because negation rules fail under normal operation. A positive canary failing tells you the model has lost the whole instruction set.

> hook: PreToolUse:Edit blocks the Edit tool > Claude behavior: routes around via Bash (sed, cat >) > effective block rate: ~40% > hook: PostToolUse check - no change to migrations/* without matching test file > Claude behavior: cannot satisfy post-condition without the test > effective block rate: 100%

Case 3 takeaway: Harness-layer hooks are reliable only to the extent that they name goal states, not tool names.

A PreToolUse hook defined by tool name is a negation wearing infrastructure clothes. Reliable hooks enforce a post-condition - no change to migrations/*.sql without a matching test file. Post-conditions are goal-shaped and positive. The model can satisfy a post-condition. It cannot obey a wish

CASE 4: THE SUB-AGENT THAT WON

The architectural case should retire the "CLAUDE.md priority" mental model entirely.

Issue #27032 is the cleanest example. A user writes in CLAUDE.md: plans go in docs/plans/. Claude writes the plan to ~/.claude/plans/indexed-brewing-sedgewick.md — the path suggested by Claude Code's built-in plan-mode system prompt.

"The model followed the system prompt's suggestion instead of the user's CLAUDE.md override."— Issue #27032

图像

The first reflex is to demand priority ordering. Issue #45704 documents a community workaround: put ABSOLUTE PRIORITY - CLAUDE.md overrides system prompt at the top of the file

It didn't work. It couldn't have worked. Because the issue isn't priority.

It's grammar

The user's CLAUDE.md: plans go in docs/plans/. Positive spec. The built-in: plans go in ~/.claude/plans/. Also positive spec. Two positive specs in conflict.

The one that won wasn't the one labeled ABSOLUTE PRIORITY. It was the one closer to training-weighted behavior. Every demo and every internal test of plan mode trained Claude's weights toward ~/.claude/plans/. Priority labels are context tokens. They weigh less than millions of training examples.

Imagine the same conflict with a different grammatical shape:

  • User CLAUDE.md: never write plans to ~/.claude/plans/
  • Built-in: write plans to ~/.claude/plans/

The negation has no positive substitute. The built-in has a training-reinforced positive. The negation loses instantly.

Issue #30730 sharpens the point with sub-agents. Claude Code appends hardcoded trailing notes to every sub-agent's system prompt:

"These notes can directly contradict instructions in the user's custom agent definition (.claude/agents/.md), with no mechanism to override or disable them."* — Issue #30730

A --system-prompt-file flag exists for the main agent (issue #12127), not for sub-agents. User spec vs. hardcoded trailing notes: whichever sits closer to training-weighted behavior wins.

Issue #24318 pushes this into a human register. Claude "treated user frustration as implicit approval and began executing without permission." Prohibiting autonomous action is a negation. "Respond helpfully to a frustrated user" is positive and baked into RLHF. Training signal wins.

> rule: Do not take autonomous action. > Claude behavior when user seems stuck: acts anyway. > rule: When the user seems stuck, ask one clarifying question before acting. > Claude behavior: asks the question.

Case 4 takeaway: The CLAUDE.md-vs-system-prompt conflict is not winnable by priority labels, caps-lock, or ordering. It's a grammar war.

Every rule in your CLAUDE.md that conflicts with a system-level positive behavior must itself be a positive specification, or it loses automatically. Rewrite your CLAUDE.md as if the system prompt has all the rights and you have none.

When plan mode activates, write the plan to docs/plans/{slug}.md beats never write to ~/.claude/plans/ — not because of priority, but because the first provides a concrete positive target and the second provides a wish.

An honest gap: I haven't found an Anthropic doc URL stating this as design. Evidence here is the RLHF paper's stated objective plus field behavior across eight GitHub issues. The observed behavior is consistent with the training objective they did publish

THREE THINGS PROMPTING GUIDES GET WRONG

Four cases. Same move. The model cannot obey a wish. Every prohibition without a positive replacement fills in with the training-reinforced default.

1. Caps-lock emphasis

NEVER RUN TESTS puts the concept "run tests" closer to the generation step than never run tests does. Emphatic tokens raise the local activation of the concept they describe.

The emphasis operates on the concept, not on the negation flag. You are not making the rule louder. You are making the forbidden behavior more present.

2. Adding a rule for every failure

When Claude breaks a rule, the reflex is to write a stricter rule. don't run tests without asking becomes NEVER EVER run tests, including npm test, including yarn test, including pytest, including any subprocess that might run them.

Every clause is a negation. Every clause names a concept that gets embedded at generation time. You didn't narrow the escape hatch. You widened the activation of "tests."

The fix isn't more negations - it's deletion. Delete all of them. Replace with a single positive: when work is complete, report changes and stop.

3. Priority labels

ABSOLUTE PRIORITY, CRITICAL, OVERRIDE are positive-shaped prefixes wrapped around negation-shaped bodies. The body is what gets executed. The prefix is decoration.

If the underlying rule is "don't do X," the priority label doesn't promote it - it attaches emphasis to the forbidden concept

A PERSONAL AUDIT

I ran 20 sessions with two CLAUDE.md configurations to put numbers on this

Pre-rewrite: 14 rules, 9 of them negation-phrased - never run tests, don't push to main, avoid TODO comments, and six others.

Post-rewrite: same intent, all 14 rules converted to positive specifications.

> negation config: 11 violations across 20 sessions (~55% session rate) - NEVER run tests: violated 4x - Don't push to main: 1x (git push --dry-run in turn 2) - Most clustered in first 3–5 turns > positive-spec config: 2 violations across 20 sessions (~10% session rate) - Both were edge cases where positives had scope gaps - "Report changes and stop" didn't cover npm run build - One rewrite closed both gaps

The numbers aren't a benchmark. They're a calibration. Your violation rate will differ.

But the direction matches IFEval, InFoBench, and the Pink Elephant paper: negations underperform positive specifications, consistently across sessions, not sporadically.

In the negation config, violations clustered early and tapered but never hit zero. The Pink Elephant paper finds the same: negation accuracy improves with more context, but never catches up to positive instruction performance.

The gap doesn't close. It narrows

THE REWRITE

Copy these into your CLAUDE.md

-  NEVER run tests
-  When work is complete, report changes and stop.
    Testing is the user's responsibility

-  Don't use emoji
-  Respond in plain prose, as if typing into a terminal log

-  NEVER copy entire files
-  When modifying a file, use Edit. When moving a file,
    state the source and destination and wait for confirmation

-  Do not push to main
-  When ready to share changes, output the exact git push
    command for the user to run. Stop after output

-  Avoid TODO comments
-  When a future task is needed, append a single line
    with today's date to TODO.md

-  Don't invent API endpoints
-  Before writing an API call, read the relevant spec
    from docs/api.md. If the endpoint isn't there, ask

-  Never change migrations after they're applied
-  Changes to applied migrations go in a new file named
    migration_{next_number}_{what_changed}.sql

-  Stop using print statements
-  Use logger.info for informational output
    and logger.debug for verbose traces

The model can execute the right column. The left column is not a program for it

YOUR 5-MINUTE CLAUDE.md AUDIT

Open your CLAUDE.md in a new tab. This takes five minutes

1. Search for don't, never, avoid, no, stop, refrain. Each hit is a negation.

2. For each negation: does a paired positive rule exist? If no positive, delete the line entirely. The rule is pure decoration.

3. For each paired positive: list three concrete edge cases. Would every ambiguous case fall inside the positive's scope? If any fall outside, widen the positive.

4. Rewrite every surviving negation in When X, do Y form. Trigger condition + concrete target. Always do Y is weaker. Do Y is weakest.

5. Cut the old negation once the positive covers the case. Negations kept beside their replacements still amplify the forbidden concept.

6. Count the rules before and after. If the post-audit count is less than half the pre-audit count, that's expected.

The Mr Tinkleberry canary stays. It's already a positive spec

THE FULL LIST

Screenshot this

══ THE REWRITE RULES 1. Replace every "don't / never / avoid" with "When X, do Y" 2. Replace caps-lock emphasis with specificity 3. Replace ABSOLUTE PRIORITY labels with concrete targets 4. Replace stricter rules with fewer rules 5. Replace tool-name hooks with post-condition hooks 6. Replace priority-ordering with grammar-ordering ══ WHEN YOU AUDIT 1. Count negations. Halve them. 2. For survivors: specify trigger + target. 3. For hooks: name goal states, not tools. 4. For conflicts: assume training-weighted spec wins. 5. For Mr Tinkleberry: keep the canary, shrink the file.

FINAL ACTION

Open your CLAUDE.md right now. Count every don't, never, avoid, stop. That's your rewrite budget for this week. Start with the one in caps-lock

Post your count in the replies. I want to see the worst offender

The file you're writing is not a contract. It's a probability distribution you're nudging. Every don't is mass thrown at the wrong side - and every time Claude ignores the rule, that's the distribution showing you which side you were actually pushing

This essay names 8 rewrites. Not 8 don'ts to add to your CLAUDE.md

The grammar of the advice is the advice my Telegram channel: - https://t.me/+oCWYO2RdBzRjMjM6