Why coding agents ignore your instructions
Your instruction file says stop and the agent does it anyway. The mechanics behind that, and a diagnosis you can run before you rewrite the rule again.
Why coding agents ignore your instructions
Coding agents ignore your instructions for four reasons, and none of them is that you were too polite. The rule was never in the context window. The rule was too vague to check an action against. Something else in the context contradicted it, usually the code the agent had just read. Or the rule is still loaded but sits far behind the current turn, and the agent is working from what is near.
Each cause has its own fix, so the first job is telling them apart. Capital letters and the word IMPORTANT are not a diagnosis. The mechanics below use Claude Code as the worked example, because its loading and compaction behaviour is documented in detail as of August 2026. Other tools differ in the details and behave the same way in outline.
Two terms first. The context window is the block of text the model sees on a given turn: system prompt, your instruction files, the conversation, and every file the agent has read. The harness is the program around the model, the thing that reads files off disk and assembles that block. Almost every complaint in this post is really a complaint about the harness, not the model.
Your instruction file is a message, not a setting
An instruction file is not configuration. Nothing in the runtime reads CLAUDE.md and enforces it. The harness reads the file off disk and pastes the text into the conversation. In Claude Code that content is delivered as a user message placed after the system prompt, which means the model sees your rules the same way it sees anything else you typed.
That has an uncomfortable consequence. Your rules compete with every other piece of text in the window, on equal footing. A rule is a claim. The file the agent just opened is evidence. When the two disagree the evidence often wins, and nothing raises an error, because from the model's point of view nothing went wrong.
The official documentation says this plainly: instruction files are treated as context, not enforced configuration. To block an action regardless of what the model decides, you need a hook, not a sentence. Hold on to that line. Most of the fixes at the end of this post are that line applied to a specific case.
Which instruction files load, and when
Claude Code walks up the directory tree from the directory you started it in. Every CLAUDE.md and CLAUDE.local.md from the filesystem root down to your working directory loads in full at launch. They are concatenated in that order, so the file closest to where you launched is read last, and within one directory the .local file is appended after the main one.
Files in subdirectories below your working directory behave differently. They do not load at launch. They load when the agent reads a file in that directory. The same is true of path scoped rules in .claude/rules/ that carry a paths: frontmatter field: they enter the context when a matching file is read, not on every turn.
That one difference explains a large share of reported failures. You put a rule in packages/api/CLAUDE.md, you ask a question about the API, and the agent answers without ever opening a file under packages/api/. The rule was not ignored. It was never present. If your repository splits guidance across per package instruction files in a monorepo, this is the first thing to check, every time.
One more loading trap, and it is the most common version of "the agent ignored my instructions": Claude Code reads CLAUDE.md, not AGENTS.md. A repository that standardised on AGENTS.md and has no CLAUDE.md gives Claude Code nothing to load at all. The supported bridge is a CLAUDE.md whose first line is @AGENTS.md, which imports the file at launch, with any Claude specific notes underneath. A symlink works too when you have nothing extra to add. Deciding what belongs in that file in the first place is a separate question, covered in splitting agent instructions from human documentation.
Confirm the file loaded before you rewrite it
Do not touch the wording until you have proof the agent can see the file. There are two checks, and the cheap one comes first.
Run /context inside the session. It prints the current window broken down by category, and the Memory files list names every instruction file that actually loaded. A file missing from that list is not in the conversation, so nothing you write inside it can matter. /memory lists the file locations and opens them for editing, including ones that do not exist yet.
For a harder answer, log the loads. The InstructionsLoaded hook event fires every time a CLAUDE.md or a rules file enters the context, and its matcher tells you why the load happened: session_start, nested_traversal, path_glob_match, include, or compact. Put this in .claude/settings.json:
{
"hooks": {
"InstructionsLoaded": [
{
"matcher": "nested_traversal",
"hooks": [
{
"type": "command",
"command": "cat >> /tmp/instructions-loaded.log"
}
]
}
]
}
}The hook receives its payload as JSON on standard input, so cat appends the whole record. Watch it with tail -f /tmp/instructions-loaded.log while you work. The exit status of this event is ignored, so the hook can only observe, never block. If your nested file never appears in that log during a session where you expected it, stop rewording. The problem is placement.
What a long session does to your rules
Two separate effects apply here, and they need different responses.
Distance. A rule stated at turn 1 is still in the window at turn 90, now competing with 90 turns of text that is more recent and more specific to what you are doing right now. You cannot configure this away, but you can measure it. Run the same task in a fresh session. If the rule holds there and fails deep into a long session, distance is your answer.
Compaction. When the window fills, the harness summarises the conversation so far and continues from that summary. What survives is what the summariser judged important, which is not the same as what you consider important. Claude Code documents the outcome per mechanism, and the differences are large. Project root CLAUDE.md and unscoped rules are re-injected from disk after a compaction. Auto memory is re-injected from disk. Rules with paths: frontmatter are lost until a matching file is read again. Nested CLAUDE.md files in subdirectories are lost until a file in that subdirectory is read again.
Rank your instructions by that table and the fragility order falls out. A rule you only typed into chat is the most fragile thing in the session: it persists only if the summary happened to keep it. A rule in packages/api/CLAUDE.md is next, because it was loaded once, summarised away, and returns only on the next read in that directory. A rule in the project root file is the most durable, because it is re-read from disk every time.
So if an instruction must hold for a whole session, it belongs in the project root file with no paths: frontmatter. Everything else is a tradeoff you should make on purpose. Managing what stays in the context window covers /compact with a focus argument and /clear between unrelated tasks, both of which change how often the summariser gets to decide what your rules were.
Why the surrounding code beats the rule
This is the failure people describe most often and diagnose least often. Your file says database access goes through the repository layer. The agent writes a handler that calls the ORM (object relational mapper) directly. You were not ignored on style grounds. You were outvoted by evidence.
A rule describes a preference. The code demonstrates one. When the agent opens three files in the module it is about to edit and all three call the ORM directly, the context holds one abstract sentence on one side and three concrete, recent, task-matched examples on the other. Copying the local pattern is usually correct behaviour. It is wrong here only because you know something the context does not: those files are legacy.
So write that into the rule. Rules that name their own counter-evidence survive contact with a real repository. Rules that state a bare preference do not.
New database access goes throughapp/repositories/. Files underapp/legacy/still call the ORM directly. That is old code, not the pattern. Do not copy it.
The second sentence does the work. It tells the agent what it is about to find and how to read it, before it finds it. The same repair applies to any rule your repository visibly contradicts: a commit style your history does not follow, a test layout half your suite ignores, an import convention that holds in new code only. Wherever the code disagrees with the file, name the disagreement in the file.
A vague rule cannot be checked, so it cannot be followed
"Write clean code." "Do not over engineer." "Keep it simple." "Be careful with migrations." None of these can be tested against a specific action, by the agent or by you. An agent given a rule it cannot check against its own output is guessing, and you are grading the guess by feel.
Here is the test to apply to every line in your file. Write the shell command that would exit non-zero when the rule is broken. If you cannot write that command, the rule is not checkable. Compare these pairs:
- Not checkable: "Keep functions small." Checkable: "A function longer than 60 lines needs a comment above it explaining why."
- Not checkable: "Test your changes." Checkable: "Run
npm testand paste the failure count before calling a task done." - Not checkable: "Keep files organised." Checkable: "HTTP handlers live in
src/api/handlers/. Nothing else goes in that directory." - Not checkable: "Format code properly." Checkable: "Use 2 space indentation in
.tsfiles."
Size is the same problem wearing a different hat. Claude Code's guidance targets under 200 lines per instruction file and states directly that longer files reduce adherence. A 700 line file is not a firmer instruction. It is 700 lines of claims with more chances to contradict each other, and it is charged against your window on every single turn, which shows up directly in your token usage. Structuring the file so each rule sits under a heading a reader can scan is covered in writing an instruction file an agent can act on.
How to diagnose it in ten minutes
Run these in order. Skipping to the last step is how people end up with a long file of shouted rules that still does not work.
- Confirm it loaded. Run
/contextand read the Memory files list. If the file is not there, fix the location and stop. Nothing else in this list applies yet. - Reproduce in a fresh session. Start a new session and give the smallest task that should trigger the rule. Holding here but failing in a long session points at distance or compaction. Failing here too means the rule itself is the problem.
- Remove the competition. Ask for the same change in a directory whose existing code already follows the rule. If compliance comes back, the surrounding code was outvoting your sentence.
- Search for a conflict. Two files giving different guidance for the same behaviour is a documented failure: the model may pick one arbitrarily, and it will not tell you it did.
- Make it checkable and retest. Rewrite the rule with a concrete path and a condition. A large jump in compliance means phrasing was your cause.
Step 4 is one command. Grep every instruction source for the topic, not just the file you were editing:
grep -rni "migration" --include="CLAUDE.md" --include="CLAUDE.local.md" .
grep -rni "migration" .claude/rules/ ~/.claude/CLAUDE.md ~/.claude/rules/ 2>/dev/nullA hit in two files that say different things is your bug. Delete one. Do not try to rank them with stronger wording, because there is no ranking engine to appeal to.
The fixes, in order of leverage
Each step below has more leverage than the one above it, and costs more to set up. Start at the top when a rule is cheap to reword. Move down the moment a rule matters enough that occasional misses are not acceptable.
- Make the rule concrete. Name a path, a command, or a condition. Add the counter-evidence the agent will find in the repository, as shown earlier. This is free and fixes a surprising share of cases.
- Move it closer to what it governs. A nested
CLAUDE.md, a path scoped rule in.claude/rules/, or a comment at the top of the file itself. The rule then arrives in the same read as the code it applies to. Accept the tradeoff: anything loaded that way drops out at the next compaction and returns on the next matching read. - Move enforcement into a hook. Prose asks. A hook decides. Hooks run as code at fixed lifecycle events and apply regardless of what the model concludes.
- Give the rule to a deterministic tool and delete the prose. Formatting, import order, line length, banned imports, commit message shape.
ruff format,prettier --write,eslint, apre-commithook. The formatter is right every time and costs zero tokens. The sentence is right most of the time and costs tokens on every turn.
Step 3 in full. Suppose migration files must never be edited by the agent. Put this in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-migrations.sh"
}
]
}
]
}
}And this in .claude/hooks/guard-migrations.sh:
#!/usr/bin/env bash
set -euo pipefail
path=$(jq -r '.tool_input.file_path // empty')
case "$path" in
*/migrations/*)
echo "Files under migrations/ are written by hand. Stop and ask first." >&2
exit 2
;;
esac
exit 0Run chmod +x .claude/hooks/guard-migrations.sh, then start a new session and ask the agent to edit a file under migrations/. The edit is refused and your message comes back as the reason. Exit status 2 on PreToolUse blocks the tool call before it runs, and your stderr text is handed to the model as the blocking message. ${CLAUDE_PROJECT_DIR} resolves to the project root, so the hook works whatever directory the agent is sitting in. The agent does not have to agree with the rule, remember the rule, or still have the rule in context. The edit does not happen.
For a flat prohibition with no logic in it, permissions.deny in your settings does the same job with no script to maintain, and the permission modes decide what runs without asking you first. If an instruction genuinely has to sit at the system prompt level rather than in a user message, --append-system-prompt puts it there, though it must be passed on every invocation, which suits scripts better than interactive work.
What you cannot instruct away
Be clear about which half of this is yours. Placement, phrasing, conflicts between files, and file size are author problems with author fixes. The rest is model behaviour, and better wording will not remove it.
Agreement is not compliance. An agent will acknowledge a rule, restate it back to you correctly, and break it two tool calls later. The acknowledgement costs nothing and predicts nothing. Do not read it as a fix, and do not count it as a test.
Some habits are persistent. Adding comments, adding defensive error handling, writing a closing summary, running the obvious next command. These come back under a rule that forbids them, at a reduced rate rather than at zero. You can measure your own rate: run the same task ten times in fresh sessions and count the violations. Where that number needs to be zero, the rule has to leave the prompt.
Your own session becomes an example. If the agent broke the rule at turn 12 and you let it pass, that violation now sits in the context as a demonstration, and it is far more recent than the rule. Correct a violation the moment you see it. An uncorrected violation teaches the rest of the session.
An instruction file is not a security boundary. It shapes behaviour and does not enforce it. Anything where a miss is expensive, credentials or destructive commands, belongs in permissions or a hook. Keeping secrets out of reach of an agent applies the same principle to data: do not ask an agent not to read a file, arrange for the file not to be readable.
The short version. Prove the file loaded, make the rule checkable, move it next to the thing it governs, and when the miss rate still matters, take it out of prose. A rule an agent cannot ignore is a rule that was never asked of the agent.
FAQ
Why does Claude Code ignore my CLAUDE.md?
Check that it loaded before assuming it was ignored. Run /context and look at the Memory files list; a file that is not named there is not in the conversation. Instruction files are delivered as a user message after the system prompt and are treated as context rather than enforced configuration, so there is no strict compliance guarantee. Most real cases are one of four things: the file sits in a subdirectory the agent never read from, two files disagree and the model picked one arbitrarily, the rule is too vague to check an action against, or the surrounding code demonstrates the opposite of what the rule says.
Does editing the instruction file mid session change anything?
Not for the copy already in the conversation. Files above your working directory load in full at launch, so the text the model holds is the text from launch time. To pick up an edit, start a new session, or ask the agent to read the file with its normal file tools, which puts the current version into the conversation as a fresh message. After a compaction the project root file is re-read from disk, so the new version arrives at that point too.
Which file wins when a root CLAUDE.md and a nested one disagree?
Neither, reliably. Discovered files are concatenated into the context rather than overriding each other, ordered from the filesystem root down to your working directory, so the closest file is simply read last. There is no precedence engine resolving contradictions, and Claude Code's documentation states that contradictory rules may be resolved arbitrarily. Write nested files as additions that name the path they govern, and delete the contradiction instead of trying to outrank it.
Do my instructions survive /compact?
It depends on how they loaded. Project root CLAUDE.md, unscoped rules and auto memory are re-injected from disk after a compaction. Rules with paths: frontmatter and nested CLAUDE.md files in subdirectories are lost until a matching file is read again. Anything you only typed into chat survives only if the summariser happened to keep it. If a rule must hold across a whole session, put it in the project root file with no paths: frontmatter.
When should a rule become a hook instead of prose?
When the check is deterministic and the cost of a miss is higher than the cost of writing a small script. File path restrictions, required commands before a commit, and forbidden tool calls all qualify. A PreToolUse hook that exits with status 2 blocks the tool call outright and hands your stderr text back to the model as the reason, so it holds whether or not the rule is still anywhere in the context. Anything a formatter or a linter can decide should be owned by that tool and deleted from the instruction file entirely.