One AGENTS.md, no drift: prove CLAUDE.md is a real symlink and the generated files are in sync
Keep one AGENTS.md as the single source of agent rules, symlink CLAUDE.md to it, and prove with CI that the symlink is committed as a symlink (not a Windows-materialised copy) and that any generated per-tool file byte-matches a fresh regeneration from the source, so nothing silently drifts.
Run this workflow
CI-verified, 2/2 fixtures passing.
Build this with your agent
One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.
Intended Use
Anyone consolidating CLAUDE.md, .cursorrules, copilot-instructions and the rest onto one AGENTS.md. CI scaffolds AGENTS.md as the source, symlinks CLAUDE.md to it, generates a copilot file, commits, then asserts CLAUDE.md is stored in git as a symlink (mode 120000) resolving to AGENTS.md, and that the committed generated file equals a fresh regeneration from AGENTS.md. No install, no network, no key. Which agent reads which file, and Claude Code's layered CLAUDE.md memory and imports, are fenced.
Not for
- Assuming a committed symlink stays a symlink, on Windows or some git setups it materialises as a plain-text copy that then drifts, which is exactly what the mode-120000 check catches
- Hand-editing generated files, they are build artifacts; edit AGENTS.md and regenerate (wire rulesync into a pre-commit hook so nobody forgets)
- Assuming AGENTS.md gives you Claude's full feature set, a symlink is portability, not CLAUDE.md's layered memory and file imports; if you lean on those keep CLAUDE.md canonical and point AGENTS.md at it, or maintain both from one rulesync source
- Roo Code, it appears on some supported-tool lists but was archived around May 2026, use Cline instead
- Monorepos with one root file, drop a nested AGENTS.md per package (closest file wins, an explicit chat instruction overrides everything)
The Stack
Tested Against
github.com/agentsmd/agents.md (2026-07)github.com/dyoshikawa/rulesync v15 (2026-07)git@2.30+coreutils@9.xSide effects & data flow
- Network
- none, local only
- Writes
- ./proj/
- Credentials
- none required
Prerequisites
- git ≥ 2.30 (stores symlinks as mode 120000)
- coreutils (readlink); for real generation: npm i -g rulesync
Steps
- 1
Scaffold one source, symlink Claude, generate copilot, then assert no drift
AGENTS.md is the real file; CLAUDE.md is a symlink to it; the copilot file is generated from it. After committing, the check asserts CLAUDE.md is a symlink in git (mode 120000) pointing at AGENTS.md, and that the committed copilot file equals a fresh regeneration. Hand-edit the copilot file, or commit CLAUDE.md as a copy, and the check trips, which is the whole point.
rm -rf proj && mkdir -p proj/.github && cd proj git init -q cat > AGENTS.md <<'MD' # Project rules for coding agents - Setup: npm install - Test: npm test - Style: no default exports; named exports only MD ln -s AGENTS.md CLAUDE.md gen() { printf '%s\n' "<!-- generated from AGENTS.md by rulesync; do not edit -->"; cat AGENTS.md; } gen > .github/copilot-instructions.md git add -A git -c user.email=a@b.c -c user.name=x commit -qm init mode=$(git ls-files -s CLAUDE.md | cut -d' ' -f1) if [ "$mode" != "120000" ]; then echo "BAD: CLAUDE.md is committed as a regular file (git mode $mode), not a symlink; on a Windows checkout it drifts from AGENTS.md"; exit 1; fi target=$(readlink CLAUDE.md) if [ "$target" != "AGENTS.md" ]; then echo "BAD: CLAUDE.md points at [$target], not AGENTS.md"; exit 1; fi if [ "$(gen)" != "$(cat .github/copilot-instructions.md)" ]; then echo "BAD: .github/copilot-instructions.md was hand-edited; it has drifted from AGENTS.md, regenerate it"; exit 1; fi echo "one-source OK: AGENTS.md is the single source; CLAUDE.md is a committed symlink (git mode 120000) to it; the generated copilot file matches a fresh regeneration (no drift). Which agent reads which file, and Claude's layered CLAUDE.md memory, are fenced" - 2
Do it in your real repo (the parts CI cannot do for you)
Write your actual AGENTS.md, run ln -s AGENTS.md CLAUDE.md, and commit, checking git tracks CLAUDE.md as a symlink. For tools that need their own format or a mixed-OS team, npm i -g rulesync, then rulesync import --targets claudecode and rulesync generate --targets '*' --features '*', and wire the generate step into a pre-commit hook so the outputs never drift. Whether each agent actually reads the file, and whether Claude's layered memory features survive the symlink, are the fenced parts.
Eval, 2 fixtures
Last passed: verified todayone-source-okcontainstimeout 30s · max $0Expected:
one-source OK: AGENTS.md is the single source; CLAUDE.md is a committed symlink (git mode 120000) to it; the generated copilot file matches a fresh regeneration (no drift). Which agent reads which file, and Claude's layered CLAUDE.md memory, are fencedclean-exitexit_codetimeout 30s · max $0Expected:
0
Results
If you run more than one coding agent you end up with the same rules copied into CLAUDE.md, .cursorrules, .github/copilot-instructions.md and GEMINI.md, and they slowly drift apart. The fix is one AGENTS.md as the file you actually maintain (an open format stewarded by the Linux Foundation's Agentic AI Foundation, read natively by 20-plus tools), a symlink so Claude Code reads it through CLAUDE.md, and rulesync to generate the tools that need their own format. But two of those moves fail silently: a committed symlink can arrive as a plain-text copy on a Windows teammate's checkout, and a generated file is a build artifact that drifts the moment someone hand-edits it. This recipe turns both into a check.
Did this work for you?
Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.
Related workflows
- Self-host CodeWiki on private code: validate the config before you spend tokens
- Run GLM-5.2 for the bulk, escalate the hard turns to Opus 4.8
- codebase-memory-mcp: wire the knowledge graph, stop re-reading files
- OrcaRouter for coding: judge by passing tests, not by vibes
- Kilo Code: add an MCP tool and bind it to one mode
- Kilo Code: a mode that can only edit the files you let it
Liked this workflow?
Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).