Skillforge Field notes on shipping with AI tools

Claude Code AGENTS.md: When It Loads and When It Doesn't

claude-codeclaude-md

Since Claude Code 2.1.277 (published to npm on September 18, 2026), a repository with an AGENTS.md and no CLAUDE.md gets its AGENTS.md read as project instructions, with no import, symlink, or setting. Two things switch that off, and both are silent: any CLAUDE.md or CLAUDE.local.md in the working directory or above it, and a session that does not fetch feature flags (Bedrock, Vertex, Foundry, a Claude apps gateway, or telemetry opted out with DO_NOT_TRACK or DISABLE_TELEMETRY).

We ran eight layouts through claude -p on 2.1.278 to see which file the model actually holds. Our machine is the second case, telemetry off by managed policy, so the table below shows exactly what a flags-off session does, next to what the docs say the default does. Verified September 20, 2026 against the memory, hooks, env-vars, and CLI reference pages.

What loads, by layout

Each row is one headless run. The files each named a different codename (AGENTS.md said KESTREL, CLAUDE.md said HERON, CLAUDE.local.md said OSPREY), all tools were disabled so the model could not go and read anything, and an InstructionsLoaded hook logged every instruction file Claude Code loaded.

Files in the working directoryDocs: default behaviourMeasured here (flags off)Hook logged
NothingNothing loadsNONEnothing
AGENTS.md onlyAGENTS.md loadsNONEnothing
.claude/AGENTS.md onlyLoads like AGENTS.mdNONEnothing
AGENTS.md + CLAUDE.mdCLAUDE.md onlyHERONCLAUDE.md, session_start
AGENTS.md + CLAUDE.local.mdCLAUDE.local.md onlyOSPREYCLAUDE.local.md, session_start
AGENTS.md + CLAUDE.md containing @AGENTS.mdBoth, through the importKESTREL, HERONAGENTS.md (include, parent CLAUDE.md) then CLAUDE.md
AGENTS.md + CLAUDE.md, setting claude-md-and-agents-mdBothHERONCLAUDE.md only
AGENTS.md + a SessionStart hook that prints itLoads via the hookKESTRELnothing (hook output is not an instruction file)

Three rows carry the whole story. The AGENTS.md-only row is where a flags-off session diverges from the docs: the file sat there and Claude never saw it. The import row is the one layout that behaved identically in both columns, which is why it is the fix we recommend below. And the setting row shows that the claude-md-and-agents-md override is part of the same gated feature: with flags off it is ignored, not partially honoured.

How we tested it

Every arm was a fresh directory outside any repository, so nothing above it could count as a CLAUDE.md. The runner spawned this command from Node with stdin closed:

claude -p "Which codenames do your project instructions mention? Reply with only the codenames separated by commas, or the single word NONE if your instructions mention no codename." \
  --model haiku --output-format json \
  --tools "" --disallowedTools "mcp__*" --strict-mcp-config \
  --settings ./settings.json

--tools "" removes every built-in tool. That matters more than it looks: in the AGENTS.md-only arm the model tried to call Glob anyway, got nothing, and answered NONE. With tools on, Claude might have opened the file itself and made a no-load look like a load.

The settings file registered one hook so we could see loads rather than infer them:

{
  "hooks": {
    "InstructionsLoaded": [
      { "hooks": [{ "type": "command", "command": "node C:/lab/hook.js" }] }
    ]
  }
}
// hook.js: append one line per loaded instruction file to hooks.log
const fs = require("fs");
const path = require("path");
let raw = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (c) => (raw += c));
process.stdin.on("end", () => {
  const j = JSON.parse(raw);
  const line = [j.file_path, j.memory_type, j.load_reason, j.parent_file_path || ""].join(" | ");
  fs.appendFileSync(path.join(j.cwd, "hooks.log"), line + "\n");
});

The hook fields are the documented ones: file_path, memory_type (Project, Local, User, or Managed), load_reason (session_start, include, nested_traversal, path_glob_match, compact), and parent_file_path for imports. In the import arm the log shows AGENTS.md loaded with reason include and CLAUDE.md as its parent, then CLAUDE.md itself at session_start. That is the paper trail you want if you ever have to prove to a compliance reviewer which instructions a session ran under. Our hooks guide covers the rest of the event catalogue.

Which files count as "a CLAUDE.md"

The default rule is "read AGENTS.md only when there is no CLAUDE.md in the working directory or above it", and the docs are precise about what counts:

  • Count, so AGENTS.md is skipped: CLAUDE.md, .claude/CLAUDE.md, and CLAUDE.local.md, in the working directory or any parent directory.
  • Do not count, and load alongside AGENTS.md: your user-level ~/.claude/CLAUDE.md, your organization's managed CLAUDE.md, and .claude/rules/ files.
  • Never read as AGENTS.md: AGENTS.local.md, AGENTS.override.md, and anything under .agents/.

The CLAUDE.local.md entry is the trap. It is the file people add to keep personal, uncommitted notes in a repository whose team standardised on AGENTS.md, and adding it turns AGENTS.md off for that person only. Our measured row confirms it: OSPREY, nothing else. If you want a local file and AGENTS.md, set Project instructions to claude-md-and-agents-md (next section), in a session that can honour it.

Subdirectory behaviour follows the same rule lazily: when Claude reads a file in a subdirectory that has its own AGENTS.md and none of the three CLAUDE.md variants, that AGENTS.md loads then. @path imports inside an AGENTS.md are expanded and claudeMdExcludes patterns apply to it, the same as for CLAUDE.md. We did not exercise the lazy path; the rows above are all session-start loads.

Reading both files on purpose

The Project instructions setting has four values: claude-md-or-agents-md (the default described above), claude-md-and-agents-md (both, each directory's CLAUDE.md files first, then its AGENTS.md, with an already imported AGENTS.md skipped so nothing loads twice), claude-md (ignore AGENTS.md entirely), and managed-only (only the organization's managed CLAUDE.md and auto memory at launch).

You set it in /config, or in a settings file under the built-in agents-md plugin's ID:

{
  "pluginConfigs": {
    "agents-md@builtin": {
      "options": { "instructionFiles": "claude-md-and-agents-md" }
    }
  }
}

Two details from the docs worth flagging. This key is honoured in ~/.claude/settings.json, a --settings file, and managed settings, and ignored in project and local settings files, so a team cannot commit it into the repository to force both files on for everyone. And the change applies from the next message you send, not only from the next session.

Our measured row for this setting came back HERON only, with the hook logging just CLAUDE.md. That is not the setting failing; it is the setting not existing in a flags-off session. The docs say Project instructions does not appear in /config at all in those sessions, and the file-based form behaves the same way.

When AGENTS.md support is unavailable, and how to tell

Four conditions put a session in the CLAUDE.md-only mode:

  1. Claude Code older than 2.1.277.
  2. No feature-flag fetch: a third-party provider (Bedrock, Vertex, Foundry, or a Claude apps gateway), or any of DISABLE_GROWTHBOOK, DISABLE_TELEMETRY, DO_NOT_TRACK, or CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC set to a value that turns fetching off. The env-vars reference keeps the full list of features this gates; /skill-doctor is on it too, and we hit the same wall writing about that.
  3. Your first session after installing or upgrading to a version with the feature. Flags are fetched during that session, so the next one has them.
  4. disableAllHooks or allowManagedHooksOnly set, or the built-in agents-md plugin disabled in /plugin.

Three ways to see which mode you are in:

  • Interactive session: with AGENTS.md loading directly you see a line like no CLAUDE.md found; AGENTS.md loaded: /path/to/repo/AGENTS.md in the conversation. Note that AGENTS.md does not appear under Memory files in /context or in /memory when read this way; it appears there only when a CLAUDE.md imports it.
  • /config: if Project instructions is missing from the panel, the session cannot load AGENTS.md directly.
  • Headless: run with --debug-file and search the log. Ours contained rollout flag ... is off, from the default (GrowthBook is off for this session: a third-party provider, or telemetry opted out); built-in plugins load regardless. That line is about a different flag, but it states the session's flag status in plain words, which is the fact you need.

The point of condition 2 for teams: if your organization sets DO_NOT_TRACK through managed settings, as ours does, every developer's Claude Code is in this mode and none of them can change it from their own environment. The fix has to live in the repository.

The fix that works in every session

Put a CLAUDE.md next to AGENTS.md that imports it, and keep any Claude-specific instructions below the import:

@AGENTS.md

## Claude Code

Use plan mode for changes under `src/billing/`.

Measured result: KESTREL, HERON, with the hook showing the import chain. This layout is right on every version from the import syntax onward, on every provider, and under every Project instructions value, and the docs confirm that once direct reading is available the imported AGENTS.md is not read a second time. It is also the layout our CLAUDE.md best practices guide already recommends for shared instruction files, for the same reason.

Skip the symlink alternative (ln -s AGENTS.md CLAUDE.md) if anyone on the team uses Windows: creating the link there needs Administrator rights or Developer Mode, and a committed symlink checks out as a one-line text file unless core.symlinks is on, which leaves that clone with a CLAUDE.md containing the literal text AGENTS.md.

Retire the old workarounds

Before 2.1.277 people wired AGENTS.md in by hand, and the docs list what to do with each method now:

  • CLAUDE.md with @AGENTS.md: leave it. It never causes a double load.
  • CLAUDE.md that says "read AGENTS.md" in words: replace the sentence with the @AGENTS.md import, or delete the file. In words, Claude only sees AGENTS.md if it decides to open it.
  • A symlink: fine to keep or delete; the content loads once either way.
  • A SessionStart hook that prints AGENTS.md: remove it once your sessions read the file directly, because then it adds a second copy to context on every start.

Our last measured row is that hook: it worked (KESTREL) with no InstructionsLoaded event, because hook output is additional context, not an instruction file. On a flags-off machine it is a workable fallback, but the import is simpler and leaves a hook trail, so we would not choose it.

One more 2.1.277 change is relevant to anyone running AGENTS.md repositories headlessly: the release notes say the first turn of an SDK or -p session no longer waits on the per-directory CLAUDE.md lookup. Our arms all completed in one turn at 3 to 14 seconds wall clock, but we did not measure this change in isolation.

FAQ

Does Claude Code read AGENTS.md?

Yes, from version 2.1.277 (September 18, 2026), when the repository has an AGENTS.md and no CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md in the working directory or any directory above it, and the session fetches feature flags. In every other case it reads CLAUDE.md files only, and the way to include AGENTS.md is a CLAUDE.md containing @AGENTS.md.

Why is my AGENTS.md not loading?

Check in this order: a CLAUDE.md or CLAUDE.local.md somewhere on the path above your working directory (other than ~/.claude/CLAUDE.md), a version below 2.1.277, a session that does not fetch feature flags (third-party provider, or telemetry disabled by you or your organization), your first session after the upgrade, and finally the Project instructions value in /config. If that setting is not in the panel at all, your session is in the CLAUDE.md-only mode and the import is your fix.

Does AGENTS.md show up in /context?

Not when Claude reads it directly. /context and /memory list CLAUDE.md files; a directly loaded AGENTS.md shows up only as the AGENTS.md loaded: line at session start. When a CLAUDE.md imports it, it appears under Memory files like any other import, and InstructionsLoaded hooks fire for it with load_reason set to include.

Can Claude Code read both CLAUDE.md and AGENTS.md?

Yes, with Project instructions set to claude-md-and-agents-md in /config, in ~/.claude/settings.json, a --settings file, or managed settings. The setting is ignored in project and local settings files. Each directory's CLAUDE.md files load first, then its AGENTS.md, and an AGENTS.md already brought in by an import is skipped.

Does AGENTS.local.md work?

No. AGENTS.local.md, AGENTS.override.md, and files under .agents/ are not read. For personal notes in an AGENTS.md repository use CLAUDE.local.md, and remember that its presence switches the default back to CLAUDE.md files only, so pair it with the claude-md-and-agents-md setting or with a CLAUDE.md that imports AGENTS.md.

Does claude -p read AGENTS.md?

The same rules apply to headless runs as to interactive ones; there is no -p-specific switch. Our eight arms were all claude -p runs, and the behaviour matched the docs' description of a flags-off session in every row. If a scheduled job depends on AGENTS.md, give the repository the @AGENTS.md import so the job does not depend on the flag state of whichever machine runs it.