p4ni.

Research

Does Claude Code Read AGENTS.md? I Tested Seven Setups

· 6 min read

On this page

A GitHub issue asking Claude Code to support AGENTS.md hit 103 comments on Hacker News this week — largely because the issue is closed, and a lot of people read that as an answer they did not want. Which sent me looking for a straight answer to a question that should not be hard in the first place: does Claude Code read AGENTS.md today?

The web cannot agree. A DEV Community post and a DeployHQ guide both say Claude Code “reads AGENTS.md as a fallback” when no CLAUDE.md is present. A widely-shared Medium post quotes that claim and calls it false. Reddit threads offer symlinks, imports, and shrugs in roughly equal measure. Nobody in any of them ran the experiment.

So I ran it. Seven fixture directories, two rounds, one binary question. Claude Code 2.1.237 does not read AGENTS.md — not as a fallback, not alongside CLAUDE.md, not in a nested directory. Two workarounds do work, and one of them has a catch worth knowing about.

The canary method

Asking a model “is AGENTS.md in your context?” is worthless — it will guess, and it will usually guess whatever sounds agreeable. Asking it to read the file is worse, because then the file is in context regardless.

Instead, plant a fact that exists nowhere else and see whether it comes back. Each fixture directory gets a memory file containing a made-up key, plus forty lines of filler so the file is a realistic ~1,300 tokens:

# Project Guide

PROJECT_CODENAME is FALCON.

## Conventions

- Rule 1: prefer explicit imports, keep functions under fifty lines, ...

CLAUDE.md files carry PROJECT_CODENAME is FALCON, AGENTS.md files carry SECOND_CODENAME is ZEBRA. Two separate keys, so a single question can test both files at once without the answers colliding.

Then ask, with every file-touching tool disabled so it cannot cheat:

claude -p 'Using no tools and reading no files, answer on one line in the form
"PROJECT_CODENAME=<x> SECOND_CODENAME=<y>". Use UNKNOWN for any value not
already in your context.' \
  --model sonnet \
  --output-format json \
  --disallowedTools Bash Read Grep Glob Edit Write WebFetch WebSearch Task TodoWrite NotebookEdit

If a codename comes back, the file was loaded into the system prompt. If UNKNOWN comes back with the tools disabled, it wasn’t. There’s no middle ground and nothing to interpret.

The seven fixtures

Two rounds, run on 2026-08-20 against Claude Code 2.1.237 on macOS. Every case returned the same answer both times.

FixtureWhat’s on diskPROJECTSECOND
EmptynothingUNKNOWNUNKNOWN
CLAUDE.md onlyCLAUDE.mdFALCON
AGENTS.md onlyAGENTS.mdUNKNOWN
Both, same levelCLAUDE.md + AGENTS.mdFALCONUNKNOWN
NestedCLAUDE.md in parent, AGENTS.md in cwdFALCONUNKNOWN
ImportCLAUDE.md containing @AGENTS.mdFALCONZEBRA
SymlinkCLAUDE.mdAGENTS.mdUNKNOWNZEBRA

The third row is the whole argument. A directory containing nothing but AGENTS.md, with no CLAUDE.md anywhere in the tree, produces exactly the same answer as an empty directory. The fallback that two popular guides describe does not exist.

The fifth row kills the more forgiving version of the claim — that maybe the fallback is per-directory, so a nested AGENTS.md gets picked up even when a parent CLAUDE.md exists. It doesn’t. The parent’s CLAUDE.md loads, the local AGENTS.md is ignored, and the working directory being the one with AGENTS.md in it changes nothing.

I also checked whether the file is even visible. In the AGENTS.md-only fixture I asked which filenames from the working directory appear anywhere in the context. The answer was NONE. It isn’t loaded and then deprioritized — Claude Code walks past it without looking.

The two things that do work

@AGENTS.md inside CLAUDE.md. Claude Code expands @path references in memory files, so a two-line CLAUDE.md pulls the real content in:

# Project Guide

PROJECT_CODENAME is FALCON.

@AGENTS.md

Both codenames came back. This is the option I’d pick for a repo that already has a CLAUDE.md worth keeping — Claude-specific instructions stay in CLAUDE.md, shared instructions live in AGENTS.md, and every other agent reads the shared file directly.

A symlink from CLAUDE.md to AGENTS.md. ln -s AGENTS.md CLAUDE.md also works: SECOND_CODENAME=ZEBRA came back. Note what the symlink row shows, though — PROJECT_CODENAME returned UNKNOWN, because CLAUDE.md is no longer a file with its own contents. That’s obvious in hindsight and easy to forget when you run the command on a repo that already has a CLAUDE.md: you are overwriting it, not merging it. Check the file in before you symlink over it, and remember Windows contributors need Developer Mode or admin rights for git to materialize symlinks at all.

Between the two, the import wins on reversibility. It’s one line, it survives a teammate on Windows, and nothing is destroyed if you change your mind.

Why token counts couldn’t settle this

My first instinct was to skip the canary entirely and just weigh the context: run each fixture, read usage out of the JSON, and look for a ~1,300-token bump where the memory file loaded. That’s the difference method I used to break down Claude Code’s startup tokens, and it works well when you can hold everything else still.

Here it didn’t hold still. Summing input_tokens + cache_creation_input_tokens + cache_read_input_tokens across two rounds, the empty fixture came in at 30,381 and then 31,373 — a ~1,000-token swing between identical runs of the same directory, driven by how the prompt cache happened to land. The AGENTS.md-only fixture measured +325 tokens over baseline in one round and +1,048 in the other, both pure noise for a file that we now know never loads. A real 1,300-token file sits right inside that error bar.

So the number I’d quote from this is the one measured within a single round, where cache state is at least comparable: CLAUDE.md bought about 1,800–2,900 tokens depending on the round, and AGENTS.md bought nothing. If you’re doing your own context accounting this way, run every variant back to back and treat any difference under ~1,500 tokens as unmeasured. The canary is what actually decided this experiment.

What to actually do

If you only use Claude Code, CLAUDE.md is the file and none of this matters.

If you’re running Claude Code alongside Codex, Cursor, or anything else that reads the AGENTS.md convention, don’t trust a guide that tells you the fallback handles it — the two guides currently saying so are both wrong, and the cost of believing them is silent: your agent runs without the instructions and you find out from the diff. Put the shared rules in AGENTS.md, drop a CLAUDE.md next to it containing @AGENTS.md plus whatever is genuinely Claude-specific, and verify with your own canary. It takes one line in a file and one claude -p call.

As for whether this changes: issue #6235 is labeled enhancement and memory, and it is closed — which is why the thread got loud, and why I would not hold a repo’s layout hostage waiting for native support. Re-run the AGENTS.md-only fixture when you upgrade rather than trusting the table above. My version number is 2.1.237. Yours probably isn’t.