fix(agents-md): seed the CLAUDE.md pointers claude actually reads - #306
Conversation
claude-code discovers CLAUDE.md only. It never reads AGENTS.md, so the AGENTS.md the supervisor seeds — and the canonical guide that file pointed at — reached no claude session at all. Measured on claude-code 2.1.233: AGENTS.md occurs in that binary only in its Codex importer and its /init prompt, and a headless run in a directory holding only an AGENTS.md gets none of it. Seed two one-import pointer files for a claude session, IFF absent. Two are needed because claude confines an @import per memory SCOPE: - <workingDirectory>/CLAUDE.md imports the AGENTS.md beside it by relative path. A project-scope import resolves only inside the project tree and drops a target above it silently, so this scope cannot reach /etc. - ~/.claude/CLAUDE.md imports the canonical guide under /etc. Only user scope reaches outside the project tree, and it loads whatever directory the session starts in. Each file supplies what the other cannot, so the guide loads exactly once. codex and shell sessions are unchanged: codex reads AGENTS.md natively, and nothing in a shell session reads either name. The seeded AGENTS.md drops its own @import of the guide, which no harness ever acted on — codex does not expand @path, and claude does not read the file. It names the path in prose instead. Also corrects the three places that claimed claude-code reads AGENTS.md as a CLAUDE.md fallback: the agentsMd option, the pointer comment, and the AgentsMd parameter in aws/template.yaml. Closes #305 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN
| claude reads CLAUDE.md, never AGENTS.md, so this file imports the | ||
| cross-vendor notes file beside it. Put your own notes in that AGENTS.md | ||
| rather than here: codex and opencode read it natively, and this pointer | ||
| is seeded once and then left alone. | ||
|
|
||
| @AGENTS.md |
There was a problem hiding this comment.
- why would you let Claude know it never reads AGENTS.md?
- why duplicate AGENTS.md content instead of adding a pointed to the shared user|project AGENTS.md?
What I'd expect is that any harness iterates on AGENTS.md so they don't have to relearn stuff.
There was a problem hiding this comment.
Good catch, fixed in 6c13b76 by making both files plain symlinks instead of generated pointer content:
<workingDirectory>/CLAUDE.md->AGENTS.md(relative)~/.claude/CLAUDE.md-> the canonical/etc/agent-box-guides/AGENTS.<user>.mdguide
That answers both points at once: there's no explanatory "claude never reads AGENTS.md" text to bake into the model's context anymore (nothing to explain — it's not a separate file), and zero duplication since it's literally the same bytes, not a second description of the same guide.
Verified empirically (temp dirs + headless claude -p runs, same method as the PR's own measurement table) that claude-code follows both symlink types, including one that points outside the project tree — which a claude @import explicitly cannot do. So the "project scope can't reach /etc" limitation that motivated the two-file split doesn't apply to a symlink at all; the split survives only because CLAUDE.md can point at one target and both the editable notes and the canonical guide are needed somewhere.
This is what you were asking for: AGENTS.md stays the single file every harness (including claude, through the symlink) iterates on — nothing to relearn per-harness.
lionello pointed out on review (PR #306, r3826318254) that the generated pointer files were duplicating content already in AGENTS.md/the canonical guide, and that telling claude "you never read AGENTS.md" inside content loaded into its own context was the wrong layer for that fact. A symlink resolves at the filesystem level before claude ever sees the path, so it needs no explanatory prose and carries zero duplicated content: <workingDirectory>/CLAUDE.md -> AGENTS.md, and ~/.claude/CLAUDE.md -> the canonical /etc guide. Verified empirically that claude-code follows both link types, including a symlink that escapes the project tree (unlike a claude `@import`, which can't) — so the "project scope can't reach /etc" workaround that motivated the old two-import design isn't needed for a symlink; the two-file split is kept only because CLAUDE.md can point at just one target and both the editable notes and the canonical guide are needed. Drops the claudeNotesPointer/claudeGuidePointer derivations and their generated payload files entirely, and renames the remaining env var from AGENT_BOX_CLAUDE_GUIDE_PATH to AGENT_BOX_CLAUDE_GUIDE_TARGET after it collided with an unrelated test's naive `.split("PATH=")` extraction of the unit's real $PATH. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrVE1N8hytq7GbYhEHADU7
Closes #305.
Motivation
claude-code discovers
CLAUDE.mdonly. It never readsAGENTS.md, so theAGENTS.mdthe supervisor seeds — and the canonical guide that file pointedat — reached no claude session at all. Every claude session on a box started
without the guidance about
$AGENT_BOX_URL,~/sites, secrets andself-update, and an agent's own notes in
AGENTS.mdwere equally invisible.Measurements (claude-code 2.1.233)
Each row: a temporary directory and one headless run
claude -p '<question>' --model claude-haiku-4-5-20251001, asking for acanary that only the file under test carries. A negative control with no
memory file at all answers
NONE.AGENTS.mdonlyCLAUDE.md->@AGENTS.md->@sub/GUIDE.mdCLAUDE.md-> absolute path inside cwdCLAUDE.md-> absolute path above cwdCLAUDE.md->@../fileCLAUDE.md->@~/file~/.claude/CLAUDE.md->@/home/agent/AGENTS.md->@/etc/agent-box-guides/AGENTS.agent.mdIn the 2.1.233 binary,
AGENTS.mdoccurs only in the Codex import adapter andin the
/initprompt text. It is not a memory-discovery name.The second rule the table shows: an
@importin a PROJECT-scopeCLAUDE.mdresolves only inside the project tree and drops a target above it silently. An
@importin the USER-scope~/.claude/CLAUDE.mdhas no such limit.What this changes
Two generated one-import pointer files, seeded IFF absent, for a claude
session only:
<workingDirectory>/CLAUDE.mdimports theAGENTS.mdbeside it by relativepath — the agent's own editable notes. Project scope cannot reach
/etc.~/.claude/CLAUDE.mdimports the canonical guide under/etc. Only userscope reaches outside the project tree, and it loads whatever directory the
session starts in.
Each file supplies what the other cannot, so the guide loads exactly once.
Both follow
users.<name>.agentsMd:nullseeds neither.The seeded
AGENTS.mddrops its own@importof the guide. No harness everacted on that line — codex does not expand
@path, and claude does not readthe file — so it now names the path in prose instead.
Verified with the exact shipping layout (the three golden payloads in place,
real
/etcguide, real claude): one run answered with both the notes canaryand the guide's section title.
Unchanged
AGENTS.mdnatively and get noCLAUDE.md.CLAUDE.md, or ahand edit, survives every respawn.
Note for follow-up (pre-existing, not changed here): a codex session still
does not LOAD the canonical guide, it only sees its path in
AGENTS.md,because codex does not expand
@pathimports.Docs corrected
Three places claimed claude-code reads
AGENTS.mdas aCLAUDE.mdfallback:the
agentsMdoption description, the comment aboveagentsMdPointer, andthe
AgentsMdparameter inaws/template.yaml.Checks run (aarch64, this box)
nix run .#assemble— regeneratedmodules/agent-box.nix, committed.nix run .#update-golden— new payloads plus the two env vars on the agentunits; diff reviewed.
nix build .#checks.aarch64-linux.{module-generated-up-to-date,golden-snapshot,multi-user,module-single-file,download-route,webhook-route}— all pass.nix eval .#checks.x86_64-linux.{sessions,memory-protection,settings-page,webhook}.drvPath— all evaluate.testScriptgates (ty check,ruff check --select F) onthe new
tests/sessions.nixsubtest — clean.New coverage
tests/sessions.nixgains a subtest: the boot claude session seeds all threefiles with the right owner and mode and the right import lines; a directory
that already holds both keeps its own content; a codex session gets
AGENTS.mdalone; a shell session gets neither.🤖 Generated with Claude Code
https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN