Every time I open a new repo, I have to decide which AI coding assistant is going to read it.
Claude Code wants CLAUDE.md. Gemini CLI wants GEMINI.md. Cursor and OpenCode read AGENTS.md. GitHub Copilot reads .github/copilot-instructions.md. Junie wants .junie/AGENTS.md. There are seventeen of these tools now, give or take, and they all expect their instruction file at a specific path with a specific name. None of them agree on what that path or name should be.
So you end up with four near-identical files in every project, all saying “prefer functional style, avoid comments that restate the code, use table-driven tests” …and then they drift. You update CLAUDE.md after a refactor and forget to update AGENTS.md. Now Claude and Codex have different opinions about your codebase. The AI assistants start disagreeing with themselves across tool boundaries. It’s a stupid problem and it wastes a surprising amount of brain.
The fix: one real file, many symlinks
agentlink is a tiny Go CLI that keeps these files in sync the boring way. You pick one file as the source (I use ~/AGENTS.md and every other tool-specific file becomes a symlink pointing back to it.
AGENTS.md # the real file you edit
CLAUDE.md -> AGENTS.md
GEMINI.md -> AGENTS.md
.cursorrules -> AGENTS.md
.github/copilot-instructions.md -> ../AGENTS.md
Edit the source once, every tool sees the change. No codegen, no templates, no surprise diffs. The symlinks are plain filesystem symlinks, the same mechanism that’s been in Unix since 1978.
That’s the whole idea. The reason this works instead of being laughably primitive is that every AI tool I’ve tested follows symlinks without complaining. They just read “the file at this path” and a symlink is a file at a path.
{% github snapsynapse/agentlink %}
What the fork adds
agentlink already existed as a two-command core (init and sync) written by Martin Mose Facondini (THANK YOU!) from before the Agentic Revolution kicked off, waaaaay back in August 2025. It did the minimum version well: read a yaml config, create the links, be idempotent. I forked it because I wanted it to handle five things the core didn’t that have since become much more important:
🔍 agentlink detect
Walk the system and report which AI tools are actually installed. It checks ~20 tools by path (~/.claude, ~/.codex, ~/.gemini, etc.) and by $PATH commands (claude, codex, cursor, aider). Pass --generate and it writes a starter .agentlink.yaml based on what it found. Removes the step where you have to remember which tools you use before writing the config.
📁 agentlink scan [dir]
Given a directory (default ~/Git), walk into every git repo that contains an AGENTS.md and wire up the missing symlinks in-place. Useful once, when you first install agentlink and want to retrofit your whole code directory. Supports --dry-run so you can see what it would do first.
🪝 agentlink hooks install
Install automatic sync triggers so the command runs without you thinking about it. It installs three things:
- A git hooks path (
post-checkoutandpost-merge) viacore.hooksPath, so sync runs after every branch switch and pull. - A zsh
chpwdhook, so sync runs every time youcdinto a repo. - A macOS launchd agent that runs sync every 60 minutes as a heartbeat.
All three are wrapped in # >>> agentlink >>> / # <<< agentlink <<< markers so agentlink hooks remove can clean them up without touching anything else you’ve edited.
💾 sync --backup
Before replacing an existing CLAUDE.md with a symlink, back it up to CLAUDE.md.bak (or CLAUDE.md.<timestamp>.bak if a .bak already exists). Empty files get skipped with a warning — no point backing up zero bytes. This sounds obvious but the original sync happily overwrote whatever was there; it was great until it wasn’t.
🌍 Global config
A second config file at ~/.config/agentlink/config.yaml that lets you sync files across your home directory, not just within a single repo. This is the one I use most — it keeps ~/.claude/CLAUDE.md, ~/.codex/AGENTS.md, and ~/.config/opencode/AGENTS.md all pointing at a single ~/AGENTS.md that captures my personal working style across every project.
The tech stack
- Go 1.23+, single binary, no runtime deps.
- Standard library only for filesystem ops (
os.Symlink,os.Readlink,filepath.Walk). gopkg.in/yaml.v3for config parsing.- Cobra for CLI scaffolding.
The only non-obvious decision: I kept the symlink creation logic in a separate internal/symlink package with its own tests, because the “is this path safe to replace with a symlink” check has a lot of edge cases (existing real file, existing broken symlink, existing symlink pointing somewhere else, target doesn’t exist yet) and I wanted them all exercised by the test suite rather than discovered in production on someone’s repo.
Why not just use a script?
Because the script is 30 lines and the edge cases are 300. Backup-before-replace, skip-empty-files, dry-run mode, detect-tool-presence, core.hooksPath compat with existing hook dirs, launchd plist generation, config-file discovery walking up from cwd — every one of those is two-line logic plus a test. After the fourth version of the bash script I’d written over two years, I gave up and did it properly.
I also wanted the detect command. No bash one-liner gives you “is Claude Code installed on this machine,” because “installed” means different things to different tools — a config dir for some, a $PATH binary for others, a macOS app bundle for a third group. A real registry with a typed schema was the right shape.
Try it
go install github.com/snapsynapse/agentlink/cmd/agentlink@v0.2.0
# In a repo with an existing AGENTS.md:
agentlink init
agentlink sync
# Or for the whole home directory:
agentlink sync --global
There’s a landing page at agentlink.run with the full tool list and config examples. MIT licensed. The fork is at github.com/snapsynapse/agentlink; upstream is martinmose/agentlink, and the fork additions are open as upstream PR #2 — fingers crossed they land.