Keeps the user-level agent instructions in sync with a remote git repo that stores one canonical copy. There is a single source of truth locally too:
~/AGENTS.md— the full canonical instructions (the only file that is synced)~/.claude/CLAUDE.md— a one-line stub@~/AGENTS.mdthat imports it, so Claude Code loads the same rules without keeping a duplicate copy
The repo (any git repo, e.g. a Gitea/GitHub repo like …/you/AGENTS) holds a single file.
Pull overwrites ~/AGENTS.md with it (backing it up first) and (re)writes the CLAUDE.md
stub. Push sends ~/AGENTS.md up and refuses to silently clobber remote changes.
The
@~/AGENTS.mdstub uses an absolute path on purpose: Claude Code resolves@-imports relative to the importing file, so a bare@AGENTS.mdinside~/.claude/CLAUDE.mdwould look for~/.claude/AGENTS.md(wrong file).initandpullmanage this stub for you — you don't edit it by hand.
All git plumbing lives in scripts/sync_agent_rules.py. Your job is to run the right
subcommand and handle the two moments that need a human: first-time setup and conflicts.
Set SCRIPT="$HOME/.claude/skills/sync-agent-rules/scripts/sync_agent_rules.py" and call
python3 "$SCRIPT" <cmd>.
First use — get the repo address
If ~/.agents/config.json does not exist, the tool isn't set up yet
(any command except init exits with code 2). Ask the user for the repo address,
then initialize:
python3 "$SCRIPT" init --repo-url <URL>
The URL is a normal git clone URL (e.g. https://your-git-host.example/you/AGENTS.git, or an
SSH URL like ssh://git@your-git-host.example:22/you/AGENTS.git).
init clones it, auto-detects the canonical filename (AGENTS.md, else CLAUDE.md,
else AGENTS.md to be created on first push), records ~/AGENTS.md as the local
target, and writes the ~/.claude/CLAUDE.md stub (backing up anything already there).
Don't guess the URL — the user must supply it.
Feature 1 — Pull (repo → local)
When the user wants the latest instructions:
python3 "$SCRIPT" pull
This resets the working copy to the remote, then overwrites ~/AGENTS.md with the
canonical file and ensures the ~/.claude/CLAUDE.md stub is in place, saving a timestamped
.bak of anything it replaces. Report which files were updated (and mention the backups so
the user knows edits are recoverable).
Feature 2 — Push (local → repo)
When the user wants to publish their local edits:
python3 "$SCRIPT" push
It pushes ~/AGENTS.md (the recorded push_source) — the single canonical file.
(~/.claude/CLAUDE.md is just a stub and is never pushed.)
Handling conflicts (exit code 3). If the remote changed since the last sync and its
content differs from what's being pushed, the tool prints CONFLICT, shows a diff, and
stops without touching the remote. Do NOT auto-resolve — surface the diff to the user and
let them choose:
- Keep their local version → re-run with
--overwrite-remote:python3 "$SCRIPT" push --overwrite-remote - Take the remote version instead → run
pull(this backs up and overwrites local). - Merge by hand → they can edit the file, then push again.
Checking state
python3 "$SCRIPT" status
Shows the repo URL, canonical filename, whether ~/AGENTS.md is in sync with the remote,
and whether the ~/.claude/CLAUDE.md stub is in place.
Authentication
The clone/fetch/push logic is protocol-agnostic — the repo URL can be an SSH
remote (git@host:owner/repo.git / ssh://git@host:port/owner/repo.git) or an
HTTP/HTTPS remote. The tool reuses the system's git credentials and never stores
tokens; only the auth prerequisite differs. If a command exits with code 4
(AUTH/NETWORK), the repo is unreachable or auth failed — pick the matching fix:
HTTP/HTTPS repo — set up a git credential helper for that host and authenticate once:
# pick the helper for the OS:
# macOS: git config --global credential.helper osxkeychain
# Windows: git config --global credential.helper manager
# Linux: git config --global credential.helper store
git ls-remote <repo-url> # prompts once, then caches
Beware a helper that hangs (e.g. Gitea's !tea login helper): every git call is
bounded at 60s and will report the timeout as code 4, but the real fix is to use an SSH
remote or remove the blocking helper for that host.
SSH repo — make sure the key works and the host is trusted:
ssh -T git@github.com # expect "successfully authenticated"
Add the key to your ssh-agent and the host to known_hosts if it fails.
Then retry the command.
Exit codes
0 ok · 1 error · 2 not initialized (run init) · 3 conflict (ask the user) ·
4 auth/network (fix credentials).