Claude Keeps Ignoring My AGENTS.md, and I Think I Know Why

A long list of project rules on a wall while an AI agent walks past without looking

In July I asked an agent to push a build of my personal assistant app to my phone. Routine task. I do it most days. It built the release APK, ran flutter install, and my habits, journal, and health logs were gone. Not corrupted, not partially migrated. Gone. flutter install uninstalls the app first when it feels like it, and everything my assistant had learned about me lived in app-private storage with no cloud copy I could count on.

The part that still bothers me is not that it happened. It is that the instruction telling it not to do this was already sitting in the repository, in the file the agent claims to read.

The Rule Was Already There, in Capital Letters

After the first time this happened, I did the obvious thing. I wrote the rule down. The commit is still in my history, and the message reads like an incident report because that is what it was:

flutter install uninstalled the app before reinstalling on a real device, wiping local user data (habits, journal, health logs) with no cloud backup guaranteed. Codify adb install -r / flutter run --release as the only acceptable device-deploy path going forward.

The rule that came out of it is not subtle:

Non-destructive install (MANDATORY): real user data (habits, journal, health logs) lives in app-private storage with no guaranteed cloud copy. Never uninstall to deploy. Use adb install -r <apk>, which overwrites in place. Never use flutter install on a device with real data.

I added a second one next to it, also marked mandatory, requiring a fresh backup before any install on a device with real data.

Then it happened again.

That is the uncomfortable part I want to be honest about. I did not forget to write the rule. I wrote it in capital letters, in the file that every agent working on this project loads, and it did not stop anything. The obvious fix was not a fix.

It Is Not One Model

My first instinct was to blame the model I was using that week. That does not survive contact with the evidence.

Opus 4.8 did it. Opus 5 still does it. Codex is not innocent here either. These are different companies training on different data with different post-training, and they fail the same way on the same file. When every vendor produces the same behavior, the problem is probably not the vendor.

I will give Opus 5 credit for one thing, because it is a real improvement. It is far more honest about mistakes than 4.8 was. It went through branches I had shipped and found bugs dating back to sprints that 4.8 had done, and it said plainly that the earlier reasoning was wrong. Older models tended to defend whatever was already in the file. That honesty is worth something.

But honesty after the fact is not the same as following the rule up front. An agent that admits it destroyed my data is still an agent that destroyed my data.

The MEMORY.md Tell

The detail that made this click for me was not the data loss. It was something much smaller and much stranger.

Agents working in my project kept learning lessons and writing them down. Good behavior, in principle. Except they wrote them in the wrong place. One session worked out my versioning scheme and carefully documented it in a MEMORY.md file. Another put notes in some other stray markdown file it had invented.

My versioning rule was already in AGENTS.md. It had been there for months.

That is not an agent breaking a rule it read and decided to skip. That is an agent that never really absorbed the file in the first place, deriving a rule from scratch that it already had, and then filing its discovery somewhere I would never look. You end up telling it, over and over, in nearly every session: read AGENTS.md. Which rather defeats the purpose of having one.

What I Actually Think Is Happening

Here is my honest theory, and it is not flattering to me.

The first line of my AGENTS.md calls it "the operating contract for AI agents and developers." That word, contract, is where I went wrong. A contract is binding. It sits outside the negotiation and constrains it. That is not what this file is at all.

AGENTS.md is context. It goes into the same window as the task I just typed, the files the agent opened, the test output, the compiler errors, and everything else competing for the model's attention. It is not a constraint on the system. It is one more voice in a crowded room, and it is the quietest one, because it was loaded first and has nothing to do with the immediate problem the model is trying to solve.

Now look at what I did every time something went wrong. I added a rule. Twenty eight commits have touched that file since May. It started at 80 lines. By the middle of August it was 220.

Every rule I added to fix a failure made the file longer. And every line I added made all the other lines a little weaker. I spent three months responding to a problem in the exact way that made the problem worse. The rule about not wiping my data was not competing with the model's laziness. It was competing with 219 other lines I had written, most of them about branch naming and lockfiles.

If everything is important, the model has no way to know that one of those lines is about destroying my data and the rest are about tidiness.

Pruning Instead of Adding

So today I did the opposite of what I had been doing. I cut the file from 220 lines to 136.

I went through every rule and asked what it actually protects. Preferences got deleted or compressed. Anything I could enforce somewhere else, I moved out of the file. What survived is mostly rules that protect real user data or a shipped contract, and those are the only ones still marked mandatory. If everything is mandatory then nothing is, so now that word appears only where the failure mode is somebody losing their journal.

I also added a model tiering table, which is a token budget rule as much as a quality one:

Tier Use for
Haiku - mechanical Small UI changes, CRUD, tests, renames, formatting, docs
Sonnet - default Normal features, typical bugs, scoped changes, integration work
Opus - difficult reasoning Architecture, unclear root causes, concurrency, cross-cutting refactors
Fable - exceptional Only when Opus is not enough. Not routine.

I had been running the most expensive model available for everything, including renaming variables, and then wondering why I hit my weekly limit on Wednesday. Escalate when the work earns it, not preemptively.

One more rule survived the cut, and it is my favorite, because it came out of a genuinely bad week. Some Bluetooth fixes I shipped a couple months back were too narrow, and the consequences kept escalating with each patch. The root cause was an agent asserting how a third party library behaved without ever checking. So now the file says: cite current source behavior and the tests or commands that prove a change, and never imply proof from tests that were not run.

Is the shorter file working? I genuinely do not know yet. I made this change today. Ask me in a month. What I can say is that the previous strategy, adding another paragraph every time something broke, had three months to work and did not.

The Obvious Answer, and Why It Is Not Enough

The advice I expected to end this post with was: stop writing rules, put the guardrail in tooling. Make it a script the agent has to run instead of a sentence it can skip.

I went to check my own history for a clean example of that working. What I found argues the other way.

In July, unformatted code reached main and failed CI twice in a row, on two different releases. My response, on July 19, was to add another line to AGENTS.md requiring a clean format check before every push. The commit message is blunt about the gap:

Formalizes the gap that let unformatted commits reach main and fail CI twice in a row (v3.11.3, v3.12.0) — flutter analyze/test passing was mistakenly treated as sufficient, but neither checks formatting.

Here is the problem with that story. The tooling already existed. A pre-commit hook running dart format --set-exit-if-changed . had been in the repository since May, two months before those two failures. I did not lack a script. I had the script and the rule, and unformatted code still shipped twice.

Pre-commit hooks only run if somebody ran pre-commit install in that working copy, and that is a manual setup step in my README. A hook is not a wall. It is a rule that happens to be written in YAML, and it can be skipped by a fresh checkout or a flag.

The thing that actually caught the problem was CI. Not because CI is smarter, but because it runs on a machine where nobody involved gets to opt out.

There is a detail here I find funny in a bleak way. My pre-commit config has an exclusion list, and AGENTS.md is on it. The file full of rules is the one file my tooling never checks.

What I Would Tell Someone Writing One of These

  • Write rules as commands, not preferences. "Prefer" is an invitation to negotiate with something that will happily negotiate.
  • Reserve your emphasis for things that destroy data. Spend it anywhere else and it stops meaning anything.
  • Prune when you add. Treat length as a cost you are paying, not a sign of thoroughness.
  • Move what you can into tooling, but be honest about which tools can be bypassed. A local hook is a suggestion with extra steps. A gate that runs where nobody can opt out is the only kind that holds.

That last one is the real lesson, and I paid for it with my own data. The instruction file is where you put the things you cannot enforce. It is not where you put the things you cannot afford to lose, and neither is a hook somebody has to remember to install.

References

Comments

Popular posts from this blog

CockroachDB performance characteristics with YCSB(A) benchmark

Digsby is bringing out a Linux and Mac client very soon

Running CockroachDB with Docker Compose and Minio, Part 2