Use Symlinks to Keep LLM Configuration Files in Sync

Coding agents use different project memory files like AGENTS.md, CLAUDE.md, and GEMINI.md. Symlinks let them share one source of truth.

Problems

The current Coding Agent ecosystem is in a lively, fast-moving stage. Different vendors and tools make different design and implementation choices, and no single standard has settled yet.

This is not unusual. It is even reasonable. The XDG Base Directory Specification[1] says software configuration files should live under $HOME/.config, but not everyone follows it either (Claude Code, for example).

Michael Livshits[2] summarized this in Every CLI coding agent, compared. Even if we only look at Project Memory, there are already many names: CLAUDE.md, AGENTS.md, GEMINI.md, .clinerules, and so on.

AgentMCPSandboxSub-agentsHeadlessPlan ModeProject Memory
OpenCodeYesDockerYesYesYesAGENTS.md
Claude CodeYesSeatbelt/BubblewrapYesYesYesCLAUDE.md
Codex CLIYesSeatbelt/LandlockYesYesYesAGENTS.md
Gemini CLIYesSeatbelt/DockerYesYesYesGEMINI.md
Qwen CodeYesDocker/SeatbeltYesYesYesQWEN.md
AiderNoNoneNoYesNoNone
GooseYesDocker (MCP)YesYesYes.goosehints
OpenHandsYesDockerYesYesYesNone
Continue CLIYesNoneYesYesNo.continue/rules
Cline CLIYesCheckpointsYesYesYes.clinerules
WarpYesNoneNoYesYesWARP.md (reads all)

Note: OpenCode is mostly compatible with the Claude Code convention. As for Claude Code supporting AGENTS.md, who knows when that will happen: Feature Request: Support AGENTS.md. · Issue #6235 · anthropics/claude-code

Solution

If a team uses multiple agents at the same time, manually maintaining several copies of AGENTS.md/SKILL.md/... is awkward. The fix is simple: create a soft link (also called a symbolic link or symlink) for the main configuration file.

# Use AGENTS.md as the primary file and create symlinks for other agentsln -s ./AGENTS.md ./CLAUDE.mdln -s ./AGENTS.md ./GEMINI.md
# Note that mklink uses a different argument order from ln -s# Ref: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/mklinkmklink CLAUDE.md AGENTS.md

I have not opened my Windows machine in a long time…

After that, whether you edit AGENTS.md or CLAUDE.md, you are editing the same file. The maintenance cost disappears.

How It Works

A soft link is essentially a special file. If you inspect the files with eza, CLAUDE.md has file type l (symlink), while AGENTS.md has file type ..

$ eza --long AGENTS.md CLAUDE.md.rw-r--r--@ 3.5k gjx 14 Mar 17:02 AGENTS.mdlrwxr-xr-x@    - gjx 15 Mar 18:33 CLAUDE.md -> ./AGENTS.md

When an application calls open("CLAUDE.md", O_RDONLY), the kernel detects during path resolution that CLAUDE.md is a symbolic link. It reads the target (./AGENTS.md) and keeps resolving the path until it reaches the final non-symlink inode. Then it creates a kernel-level open file object for that inode and returns a file descriptor. After that, read(fd, buffer, size) reads directly from the target file. This is transparent to the application above it.

The path resolution process can be roughly illustrated by this LLM-generated Python code. Sigh, my coding ability and my ability to read non-Python code have both degraded.

vided_real_path.py
import osMAX_SYMLINK_DEPTH = 40def real_path(path):    path = os.path.abspath(path)    depth = 0    while True:        if depth > MAX_SYMLINK_DEPTH:            raise RuntimeError("symlink loop or too many levels")        if not os.path.islink(path):            return path        target = os.readlink(path)        if os.path.isabs(target):            path = os.path.abspath(target)        else:            parent = os.path.dirname(path)            path = os.path.normpath(os.path.join(parent, target))        depth += 1# Example usageif __name__ == "__main__":    print(real_path("CLAUDE.md"))
 $ python ./symlink_parser.py/Users/gjx/Library/CloudStorage/OneDrive-Personal/Documents/posts/AGENTS.md

Git Compatibility

Symlinks can be committed to Git. For example, a repository may contain a link like this:

.agents/skills -> ../.claude/skills

After commit, Git records the relative path ../.claude/skills. When someone else clones the repository on macOS/Linux, as long as .claude/skills is also inside the same repository, the symlink can still resolve to the right directory after checkout. The key is that the target path should be relative and should point inside the repository. Do not use an absolute path like /Users/xxx/.../.claude/skills, and do not point to something outside the repository. Otherwise, it can easily become a broken link on another machine.

The real caveat is Windows, or any Git environment where symlink checkout is disabled. In that case, Git may not create a real symlink. It may check it out as a plain text file whose content is ../.claude/skills.

Symlinks are everywhere in development toolchains:

  • pnpm: uses symlinks to implement a content-addressable store. The same package only needs to live once on disk, and projects share it through links.
  • bun: uses a similar symlink strategy to reduce the disk cost of node_modules. See my post on bun link, where I use bun link to simplify the workflow for developing a Library/Blog theme.
  • Homebrew: symlinks programs installed under Cellar/ into /opt/homebrew/bin/, which makes switching software versions convenient.

The earlier XDG Base Directory problem can also be handled with symlinks. You can collect configuration files scattered across ~/.config/, ~/, and other locations into one Git repository and manage them together. If you are interested, see my post on dotbot.


  1. Various specifications specify files and file formats. This specification defines where these files should be looked for by defining one or more base directories relative to which files should be located. XDG Base Directory Specification ↩︎

  2. Michael Livshits is a software engineer specializing in LLMs and AI systems. Previously Staff Engineer in ML/AI at Intuit. Founded a few startups before and after.

    He writes about LLMs, engineering, and the things he learns along the way at this blog. ↩︎