You put one line in CLAUDE.md: never push directly to main. You ask the assistant what the project rules are, and it reads that line back to you word for word. Twenty minutes into a refactor it runs git push origin main. So you write the rule in capitals, add IMPORTANT in front of it, copy it into AGENTS.md for good measure, and a week later it happens again.
The question people type after that is some version of how to stop an AI coding assistant from forgetting project rules across sessions. It did not forget. The rule arrived, the model read it, and nothing in a rules file was ever going to stop the push. That is not my reading of the situation. All three vendors say it on their own pages, and one of them tells you what to use instead.
One line to carry: a rules file asks, a permission rule or a hook refuses, and even the refusing layer has a form it does not catch and a way to fail open, so the rule that must hold every time belongs in the last layer, the one that never reads your prose at all.
Three files, three vendors, the same sentence
CLAUDE.md, from Anthropic's memory documentation:
Claude treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead.
AGENTS.md, from its own page:
A simple, open format for guiding coding agents
and a line below that:
Think of AGENTS.md as a README for agents: a dedicated, predictable place to provide the context and instructions to help AI coding agents work on your project.
Cursor rules, from Cursor's documentation, which is the most mechanical of the three:
Large language models don't retain memory between completions. Rules provide persistent, reusable context at the prompt level. When applied, rule contents are included at the start of the model context.
The same Cursor page treats the formats as interchangeable: "If you prefer plain markdown, use AGENTS.md instead."
Context. Guiding. A README. Included at the start of the model context. None of the three claims the file binds anything, and none of them is hiding it. The idea that a rules file enforces a rule is something readers bring to it, because the file is called rules.
How the rule actually reaches the model
Anthropic's memory page answers this directly:
CLAUDE.md content is delivered as a user message after the system prompt, not as part of the system prompt itself. Claude reads it and tries to follow it, but there's no guarantee of strict compliance
A user message. Not a configuration value, not a system instruction. Your rule sits in the conversation next to your actual task, next to whatever the last tool call returned, next to the file somebody else committed, and it competes with all of it on the same terms.
One person who put mitmproxy between the client and the model, while trying to understand why adherence to their CLAUDE.md had slipped, reported on Hacker News what the file was wrapped in at the time:
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
The comment is from July 2026 and describes what they saw about six months earlier, so it is their observation at that time, not a claim about today's build. It does not need to be current to make the point, because the vendor's own sentence above already says the same thing in plainer words: the model reads the file and tries.
The people who meet this in practice describe it the same way. From claude-code#87825, open at the time of writing:
The rule was in memory. Claude had read it. Claude pushed directly to main three times.
And from claude-code#75334, a different person: "Memories are written but not acted on."
Reading and obeying are two different failures
Before changing anything, find out which one you have, because they have unrelated fixes.
First, ask the assistant to state your rule back. If it cannot, the rule is not arriving, and the fix is about delivery: wrong file name, wrong directory, wrong scope, a setting that excludes it. Nothing further matters until this passes.
Second, only if the first passed, put the assistant in the situation the rule governs and watch. If it states the rule correctly and then does the thing anyway, delivery works and nothing is enforcing. That outcome is not a malfunction. There is no obedience layer to malfunction, only a sentence in a context window and a model deciding.
If the decision has to go your way every time, the decision cannot be the mechanism.
What refuses, in the vendors' own words
Anthropic's permissions page draws the line in one note:
Permission rules are enforced by Claude Code, not by the model. Instructions in your prompt or
CLAUDE.mdshape what Claude tries to do, but they don't change what Claude Code allows.
The rule that stops the push in the opening scene is a deny rule in .claude/settings.json. This is the shape the same page uses for its own example:
{
"permissions": {
"deny": [
"Bash(git push *)"
]
}
}
Two details on that page change how you should write these. A deny rule wins over everything else: "An allow rule can't carve an exception out of a deny rule." And a deny rule behaves differently depending on its shape. A bare tool name "removes the tool from Claude's context entirely, so Claude never sees it", while a scoped one like Bash(rm *) "leaves the tool available and blocks matching calls when Claude attempts them". The first is not a refusal at all. The model is never shown the tool, so there is nothing to refuse.
The refusing layer has its own gaps
This is the part most advice stops before, and the vendors document it themselves.
A Bash deny rule matches the command text, not the program. The permissions page says a deny or ask rule "covers the invocation Claude usually produces and isn't a security boundary around the program", and its own table lists what Bash(git push *) does not stop: git -C . push origin main, git -c push.default=current push origin main, git 'push' origin main. For enforcement that does not depend on the command text, the page points elsewhere: "For filesystem and network enforcement that doesn't depend on the command text, use sandboxing."
A hook sees the full command and runs your own logic on it. In Claude Code that is a PreToolUse hook, written as the hooks guide shows, and the permissions page states its precedence: "A hook that exits with code 2 stops the tool call before permission rules are evaluated". In Cursor it is a beforeShellExecution hook in hooks.json, and Cursor's hooks page lists exit code 2 as "Block the action".
And a hook can fail open. Cursor states it plainly: "Crashes, timeouts, and non-zero exit codes other than 2 fail open by default: Cursor logs the failure and allows the action through." It offers failClosed: true to change that. Claude Code's guide describes the same default in more steps: for any exit code other than 0 and 2, with plain text or nothing on stdout, "the action proceeds as a non-blocking error." The quietest failure does not even reach that branch. The guide's example scripts read the command with jq. On a machine where jq is not installed that line yields an empty string, nothing matches, the script reaches exit 0, and the command goes on as if the hook were not there.
None of this makes hooks or deny rules weak. It means each layer has a known gap, and it is worth knowing which gap you are standing in.
| layer | what it does | what gets past it |
|---|---|---|
rules file: CLAUDE.md, AGENTS.md, Cursor rules |
puts your sentence in the context window | the model deciding otherwise |
| permission deny rule | refuses the command text it matches | the same program invoked another way |
hook: PreToolUse, beforeShellExecution
|
runs your code on the full command first | a hook that crashes or times out, by default |
| the system the action lands on: a protected branch, file permissions, a sandbox | refuses whatever sent the request | nothing the assistant writes |
For never push to main, the last row is the one that holds every time: a protected branch on the hosting side, set so that nobody can bypass it, refuses the push whether it came from the assistant, from a teammate, or from you at two in the morning. The rules file is still worth keeping, because it makes the assistant try the right thing first and saves you the refused attempt. It just cannot be the thing you rely on.
When a rules file is exactly the right tool
If what you need is the same instructions in front of the model every session, a file in your repository does that. It costs nothing, you can read it, your team reviews it in a pull request, and it is already installed. For style, conventions, which test command to run and where things live, that is the whole answer and there is nothing to buy.
If what you need is for something to not happen, no rules file and no memory layer will do it, whoever sells it. Storage is not enforcement. Use the layer that runs outside the model.
A memory layer is for a narrower job than either: facts that accumulate over time, that nobody wrote down by hand, and that need to be present in a session you have not started yet, sometimes in a tool you were not using when they were learned. That is a real problem, and it is a different one from the push in the opening scene.
Which rule in your CLAUDE.md or AGENTS.md have you watched get read back correctly and broken anyway, and which of the four layers finally stopped it?
Disclosure: I work on Mnemoverse, a memory layer for AI agents that connects over MCP. Nothing of ours appears in this article, and its conclusion is partly an argument against buying a memory layer for this problem. The longer version with every source is on our library, and the MCP server is open source (MIT): github.com/mnemoverse/mcp-memory-server.
Top comments (0)