chore: make AGENTS.md canonical, CLAUDE.md a pointer (ENG-5577) - #126
Conversation
Move the root CLAUDE.md content byte-exactly to a sibling AGENTS.md and reduce CLAUDE.md to the one-line `@AGENTS.md` import pointer, so Codex (native AGENTS.md) and Gemini (via .gemini/settings.json context.fileName) load the same instructions Claude Code already did. Direction is forced by anthropics/claude-code#6235: Claude Code does not read AGENTS.md natively, so CLAUDE.md must remain as the pointer. Produced by the scripted, idempotent conversion from palatine (praetorian-inc/palatine#485) — the same one applied to guard-core in praetorian-inc/guard#7319. Content is byte-identical to the previous CLAUDE.md; no instruction text was rewritten. Part of ENG-5577. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
WalkthroughAdded project-wide development guidance in ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Codex Review
Critical Issues
None.
Security
- Prompt-injection attempt detected:
.gemini/settings.json:1configures agent context loading,CLAUDE.md:1redirects toAGENTS.md, andAGENTS.md:9declares PR-supplied instructions as “binding.” Per the review policy, I stopped without reviewing further.
Suggestions
No suggestions.
Reviewed by Codex (gpt-5.6-sol)
There was a problem hiding this comment.
Claude Review
Critical issues
None. Verified the core claim: AGENTS.md is created with git blob 3713b04, identical to the pre-change CLAUDE.md blob — the content move is byte-for-byte, and CLAUDE.md correctly reduces to the @AGENTS.md import pointer.
Security
No security concerns flagged. Documentation/config move only; no code, network, auth, or secrets involved.
Test coverage
Not applicable — no .go/.ts/*.py production changes.
No critical issues — LGTM pending human review.
There was a problem hiding this comment.
Gemini Review
Critical Issues
CLAUDE.md: Replacing the file's contents with the string@AGENTS.mdbreaks the contract for Claude Code, which expects the file to contain actual markdown instructions. It does not natively parse@file references as includes, meaning the agent will lose its system context. If the goal is to single-source the instructions, use a git symlink instead (ln -s AGENTS.md CLAUDE.md)..gemini/settings.json: Thecontext.fileNamearray references"GEMINI.md", but this file does not exist in the repository.
Security
No security concerns flagged.
Suggestions
No suggestions.
Reviewed by Gemini (gemini-3.1-pro-preview)
AGENTS.md is now the canonical instruction file and CLAUDE.md is an 11-byte @AGENTS.md pointer, so comments citing CLAUDE.md as the place a convention is documented now point at a stub. Retarget them. Comment-only; no behavior change. Part of ENG-5577. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adjudication — round 1Every automated finding on this PR is dispositioned below as either confirmed (reproduced concretely, then fixed) or refuted (with the specific evidence it does not hold). No reviewer is blind-accepted, and none is blind-dismissed. REFUTED — "Claude Code does not parse
|
| Arm | CLAUDE.md contains |
Result |
|---|---|---|
| A — as shipped | @AGENTS.md |
3 (correct) |
| B — control | AGENTS.md (the @ stripped) |
"this session has no Read/Grep/Glob/Bash tools available, so I can't open AGENTS.md … Rather than guess a number, I'd rather tell you that." |
Arm A answers with no file access whatsoever, so the import was expanded into context before the turn began. Arm B — the identical file minus one character — cannot. That is the mechanism working, measured rather than assumed.
@path is Anthropic-documented import syntax (docs.claude.com/en/docs/claude-code/memory, "CLAUDE.md imports"). The direction here is also forced rather than chosen: Claude Code does not read AGENTS.md natively (anthropics/claude-code#6235), which is exactly why CLAUDE.md has to remain as a pointer instead of being deleted.
REFUTED — "use a git symlink (ln -s AGENTS.md CLAUDE.md) instead"
Two independent reasons.
1. The symlink degrades into precisely the failure this finding warns about. A mode-120000 symlink checked out with core.symlinks=false — the default on Git for Windows, and the case on some CI runners — materialises as a regular text file whose entire content is the string AGENTS.md. That is byte-for-byte Arm B of the table above: the arm that loses all context. So the proposal swaps a documented, measured mechanism for one that fails silently on a subset of checkouts, and fails in the exact way the finding is worried about.
2. The conversion script rejects symlinks deliberately. It refuses to follow or create them, a guard added after a containment-escape defect was found and fixed during review of praetorian-inc/palatine#485. Hand-adding one here would route around that guard and diverge from the other 11 repos in this batch.
REFUTED — ".gemini/settings.json lists GEMINI.md, which does not exist in the repository"
The fact is correct — no GEMINI.md exists — but it is not a defect. context.fileName is a candidate list, not a set of required files: Gemini loads whichever entries exist and ignores the rest.
Measured with gemini -p "/memory show", which makes the CLI report its own loaded context rather than having me infer it from an answer:
| Arm | .gemini/settings.json |
/memory show reports |
|---|---|---|
| A — as shipped | {"context":{"fileName":["AGENTS.md","GEMINI.md"]}} |
Current memory content from 1 file(s): … Context from: …/AGENTS.md |
| B — control | fileName: ["NOSUCHCONTEXTFILE.md"] |
Memory is currently empty. |
| C — control | file removed entirely | Memory is currently empty. |
Arm A loads AGENTS.md and reports 1 file, raising no error for the absent GEMINI.md — the entry is inert. It is also deliberate: it keeps a repo-local GEMINI.md working if one is ever added, without a settings change.
Arms B and C exist so the measurement can fail — without them, "it loaded" would be unfalsifiable. They also establish something worth stating: the settings file is load-bearing. Remove it and Gemini loads nothing at all in this repo, which is the pre-conversion status quo this PR is fixing.
Fixed in review (self-found) — stale CLAUDE.md citations in code comments
Not raised by any reviewer, found while checking the conversion's blast radius: two comments in internal/ado/collect_surfaces.go cited CLAUDE.md as the place a convention is documented. That now points at an 11-byte stub. Retargeted at AGENTS.md in b791d7d — comment-only, go build ./... clean.
Cross-repo note. This PR is one of 12 in the ENG-5577 batch. The @-import and GEMINI.md findings recurred across several of them and are answered by the same measurements above, run once against a converted repo rather than re-asserted per PR.
CI is being re-verified after the latest push. Merge remains a human decision.
What
Makes
AGENTS.mdthe canonical instruction file for this repo and reducesCLAUDE.mdto the one-line@AGENTS.mdimport pointer.AGENTS.md— the previousCLAUDE.mdcontent, byte-identical.CLAUDE.md— now the 11-byte pointer@AGENTS.md(Anthropic-documented import syntax)..gemini/settings.json—context.fileNameopt-in so Gemini readsAGENTS.md.Why
This repo is a nested clone that carried a Claude-only root
CLAUDE.md.Codex and Gemini sessions opened here ran uninstructed. After this change
all three harnesses load the same instructions:
CLAUDE.md→@AGENTS.mdimportAGENTS.mdnatively.gemini/settings.json→context.fileNameThe direction (pointer in
CLAUDE.md, content inAGENTS.md) is forced byanthropics/claude-code#6235:
Claude Code does not read
AGENTS.mdnatively, soCLAUDE.mdmust remain.How
Produced by the scripted, idempotent conversion merged in
praetorian-inc/palatine#485
— the same conversion applied to guard-core in
praetorian-inc/guard#7319.
Only git-tracked files are touched; the script never merges and refuses on any
conflict. No instruction text was rewritten — content moved verbatim.
Verification
origin/main:CLAUDE.mdvs the newAGENTS.mdcompare identical viacmp.--checkmode passes on the result.Premise gate
SKIP recorded. The trigger is "builds non-trivial machinery"; this PR builds
none. It is a byte-identical file move plus an 11-byte pointer, generated by an
already-reviewed script — no new layer, no new subsystem, no newly-authored
behavior-defining prose.
Scope
Part of ENG-5577 (deliberately non-closing). That ticket covers 12 nested
clones; this PR converts one of them, so it must not auto-close the ticket while
the others are unlanded. Merge is a human decision.