Every AI coding tool now has a "project rules" file. Cursor has .cursor/rules, Claude Code has CLAUDE.md, OpenAI Codex has AGENTS.md. Teams write them once, watch the agent ignore half of it, and conclude the feature is broken.
Most of the time the feature isn't broken. The rules file is written for a human reader, and each tool consumes it differently. Here's how the three tools actually load rules as of mid-2026, and the writing habits that transfer across all of them.
Cursor: rules are conditionally attached, not always read
Modern Cursor rules live in .cursor/rules/ as .mdc files with frontmatter (the old single-file .cursorrules has been dropped from the current docs entirely — treat it as deprecated and migrate). The frontmatter decides when the rule enters context:
---
description: "Conventions for API route handlers"
globs:
- "src/api/**/*.ts"
alwaysApply: false
---
- Every handler validates input with zod before touching the DB
- Return typed error envelopes, never raw strings
The part people miss: there are effectively four attachment modes.
-
Always Apply (
alwaysApply: true) — in context for every request -
Apply to Specific Files (the mode older docs called "Auto Attached") — pulled in when a file matching
globsis referenced -
Apply Intelligently (formerly "Agent Requested") — the agent reads the
descriptionand decides whether to fetch the rule - Apply Manually — only when you @-mention it
So if your rule has a vague description like "general best practices" and no globs, it sits in Apply Intelligently mode and the model has no reason to ever pull it. The rule isn't ignored — it was never loaded. Give globs to anything file-scoped, and write descriptions like an API doc: what the rule covers, when to use it.
Claude Code: everything loads up front, so tokens are the budget
Claude Code reads CLAUDE.md automatically at session start: your user-level file (~/.claude/CLAUDE.md), the project root file, and files from parent directories; deeper CLAUDE.md files in subdirectories join when work touches those folders. You can also compose with imports:
See @docs/architecture.md for the module map.
The consequence of "loads every session" is that CLAUDE.md is a context tax. Every line you add is a line the model carries through the whole session, whether it's relevant to the current task or not. Long, unprioritized files don't fail loudly — they just dilute attention until the instruction you cared about loses to noise.
What works: keep the root file short and non-negotiable (build commands, hard constraints, "never do X because Y"), and push detail into imported or nested files that only load where they matter.
Codex: AGENTS.md is a merge chain, deepest wins
Codex looks for AGENTS.md in your home config (~/.codex/AGENTS.md), the repo root, and nested directories, merging as it goes — the file closest to the code being edited takes precedence on conflicts. AGENTS.md is also an open convention adopted by a growing list of tools, so it doubles as the vendor-neutral place for agent instructions.
The failure mode here is the opposite of Cursor's: everything does load, so people dump monoliths into the root file. Then a subdirectory rule silently overrides a root rule and nobody notices until the agent "randomly" changes behavior between folders. Treat the hierarchy as scoping, not duplication: repo-wide invariants at the root, module-specific conventions next to the module, and never state the same rule at two levels.
Five habits that transfer across all three
1. Write imperatives at the decision site. "Use keyword arguments for all function definitions" beats "code should be readable". The agent applies rules at the moment it makes a choice; phrase rules as the choice.
2. Explain the why on negative constraints. "Don't use merge, use rebase" gets pattern-matched away under pressure. "Don't create merge commits — this repo's tooling assumes linear history" survives, because the model can reason about when it applies.
3. Pay the token tax deliberately. All three tools ultimately stuff your rules into a context window. A 400-line rules file isn't 4x better than a 100-line one; it's usually worse. Prune anything the tool already does by default.
4. Scope rules to where they're true. Glob-attached rules in Cursor, nested CLAUDE.md in Claude Code, per-directory AGENTS.md in Codex — all three give you locality. A rule that's only true for src/api doesn't belong in the root file.
5. Test rules like code. After writing a rule, give the agent a task where the rule should fire and watch. If it doesn't fire, the fix is usually specificity or placement, not repetition. Rules files deserve version control, review, and occasional deletion — they drift out of date exactly like documentation because they are documentation, just with a machine reader.
The one-hop rule
A pattern that shows up in all three ecosystems: agents reliably follow one pointer, and quietly bail on two. A root rule that says "for API conventions, read docs/api-rules.md" works. A root rule pointing to a doc that points to another doc doesn't.
One carve-out: Claude Code's @import syntax is mechanical, not judgment-based — imports expand recursively at load time (up to a documented depth of four hops), so chained @ imports do arrive in context. The one-hop rule is about plain-text pointers, where the agent chooses whether to go read the file. If your rules need structure, either use a mechanical include where the tool offers one, or make each referenced file self-contained so a single hop is always enough.
I maintain Rulestack, ready-to-use rule packs and skills for Cursor, Claude Code, and Codex, written and updated against each tool's current spec. If you'd rather start from a working baseline than a blank file, that's what it's for.
Top comments (6)
Solid breakdown of the loading side. The half still missing across all three tools is eviction. Cursor's Apply Intelligently at least lets a rule stay dormant until its description matches, but CLAUDE.md and AGENTS.md load everything up front, so every stale rule is a permanent context tax you pay on every unrelated task. The failure mode there isn't 'rule ignored', it's 'the twenty relevant rules diluted by eighty that don't apply here'. What's worked for me: treat the always-loaded file as a cache with no eviction policy and add one. Cheapest version is a hit-counter comment per rule plus a periodic prune of anything that hasn't fired in N sessions. Writing the description like an API doc, as you say, is really just giving each rule a cache key so the tool can fetch it on demand instead of you paying to hold it resident.
The cache-with-no-eviction-policy framing is the best articulation of this I've read — and honestly a better way of putting my own "API doc" advice than I managed. "Twenty relevant rules diluted by eighty" matches exactly what I see: long rules files fail by dilution, not disobedience. I've been running a cruder version of your prune (delete anything I can't remember firing recently), and even that paid for itself fast. The hit-counter idea is a nice cheap upgrade on it.
Glad the framing was useful. One trap to wire around before you lean on the hit-counter: firing frequency measures whether a rule matches, not whether it's still earning its place. Two failure modes it misreads. A rule that fires constantly can be actively harmful, an over-tightened rule that keeps vetoing fine work still shows a high count. And a rule that never fires can be the most valuable one you have, it never fires because it successfully keeps you out of the situation, which is survivorship, not deadweight. So the signal I'd actually evict on isn't low frequency, it's 'fires but never changes the outcome,' a rule whose verdict always matches what the model would have done anyway. That's the true redundant rule, diluting the context for zero decisions. Count outcomes-changed, not matches, and the prune stops eating your load-bearing guards.
"Count outcomes-changed, not matches" is the right metric — that reframe fixes both failure modes in one move. The practical catch: outcomes-changed needs a counterfactual. You have to know what the model would have done without the rule, and you can't observe that inline. The cheapest sampler I've found is deletion testing — drop the rule on a sandbox branch, rerun a handful of representative tasks, and diff. If nothing changes, that's your "verdict always matches" rule caught red-handed. The match counter then earns a humbler job: not a value ranking, just a priority queue for which rules to deletion-test first.
I've watched this happen switching the same repo between Claude Code and Codex. Both read the rules file without complaining, so it feels like the constraint is active, but when I actually diff what shipped, one agent quietly drops the rule three files deep into a session while the other holds it. Reading the file is not the same as weighting it once context fills up with tool output and edits. I stopped trusting that the rules loaded as a signal and just started reading the diff instead.
"Reading the file is not the same as weighting it" is the sharpest one-line summary of this problem I've seen — and the mid-session part is what I underweighted in the article. A rule that fires at turn 2 can quietly stop firing at turn 40 once the context fills with tool output. Your diff-first habit matches where I've ended up too: the only honest signal is what actually shipped.