Skip to content

fix(agents-md): seed the CLAUDE.md pointers claude actually reads - #306

Merged
lionello merged 2 commits into
masterfrom
fix/305-claude-md-pointers
Aug 21, 2026
Merged

fix(agents-md): seed the CLAUDE.md pointers claude actually reads#306
lionello merged 2 commits into
masterfrom
fix/305-claude-md-pointers

Conversation

@defangdevs

Copy link
Copy Markdown
Owner

Closes #305.

Motivation

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. Every claude session on a box started
without the guidance about $AGENT_BOX_URL, ~/sites, secrets and
self-update, and an agent's own notes in AGENTS.md were 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 a
canary that only the file under test carries. A negative control with no
memory file at all answers NONE.

Layout (cwd = test dir) Canary reaches the model?
AGENTS.md only NO
CLAUDE.md -> @AGENTS.md -> @sub/GUIDE.md YES
CLAUDE.md -> absolute path inside cwd YES
CLAUDE.md -> absolute path above cwd NO
CLAUDE.md -> @../file NO
CLAUDE.md -> @~/file NO
~/.claude/CLAUDE.md -> @/home/agent/AGENTS.md -> @/etc/agent-box-guides/AGENTS.agent.md YES

In the 2.1.233 binary, AGENTS.md occurs only in the Codex import adapter and
in the /init prompt text. It is not a memory-discovery name.

The second rule the table shows: an @import in a PROJECT-scope CLAUDE.md
resolves only inside the project tree and drops a target above it silently. An
@import in the USER-scope ~/.claude/CLAUDE.md has no such limit.

What this changes

Two generated one-import pointer files, seeded IFF absent, for a claude
session only:

  • <workingDirectory>/CLAUDE.md imports the AGENTS.md beside it by relative
    path — the agent's own editable notes. Project 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.
Both follow users.<name>.agentsMd: null seeds neither.

The seeded AGENTS.md drops its own @import of the guide. No harness ever
acted on that line — codex does not expand @path, and claude does not read
the file — so it now names the path in prose instead.

Verified with the exact shipping layout (the three golden payloads in place,
real /etc guide, real claude): one run answered with both the notes canary
and the guide's section title.

Unchanged

  • codex sessions: they read AGENTS.md natively and get no CLAUDE.md.
  • shell sessions: nothing there reads either name, so neither is seeded.
  • Existing files are never touched, so a repo checkout's own CLAUDE.md, or a
    hand 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 @path imports.

Docs corrected

Three places claimed claude-code reads AGENTS.md as a CLAUDE.md fallback:
the agentsMd option description, the comment above agentsMdPointer, and
the AgentsMd parameter in aws/template.yaml.

Checks run (aarch64, this box)

  • nix run .#assemble — regenerated modules/agent-box.nix, committed.
  • nix run .#update-golden — new payloads plus the two env vars on the agent
    units; 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.
  • The driver's own testScript gates (ty check, ruff check --select F) on
    the new tests/sessions.nix subtest — clean.
  • The VM tests themselves are x86-only and run in CI.

New coverage

tests/sessions.nix gains a subtest: the boot claude session seeds all three
files 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.md alone; a shell session gets neither.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN

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
Comment thread modules/agent-box.nix.in Outdated
Comment on lines +779 to +784
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>.md guide

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
@lionello
lionello merged commit 4d857c6 into master Aug 21, 2026
2 checks passed
@lionello
lionello deleted the fix/305-claude-md-pointers branch August 21, 2026 01:25
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Agent-Box Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

claude sessions never read AGENTS.md (or the canonical guide): discovery is CLAUDE.md only

2 participants