One canonical Agent Skills tree, symlinked into every agent on your machine. Edit a skill once — Claude Code, Codex, and any other Agent Skills–compliant client see the change instantly. No copies, no drift.
I use several coding agents side by side — Claude Code, Codex, and Hermes and I wanted the skills I write to work in all
of them. My first attempt was the obvious one: copy each skill into every
agent's skills directory. It broke down fast. The same skill ended up
duplicated across ~/.claude/skills, ~/.agents/skills, and whatever
directory each new CLI decided to read; I would fix a skill in one place and,
a week later, two agents behaved differently for no visible reason.
skillsync is the setup I landed on: every skill lives exactly once in a canonical repository, and every agent gets a symlink to it. Editing the file updates all agents at the same instant, a new agent is one config line away, and drift is structurally impossible. The tooling here automates the symlinking, keeps the tree honest, and proves the agents actually discover what you shipped.
This repository is a template for your canonical skills tree. Fork or
clone it, drop your skills into skills/<name>/, list them in
skills-manifest.json, and let the tooling do the rest:
skillsync repo (the single source of truth)
└── skills/
└── my-skill/SKILL.md ◄─────────┐
│ symlinks, one per skill
~/.agents/skills/my-skill ──────────┤ (Codex + Agent Skills clients)
~/.claude/skills/my-skill ──────────┘ (Claude Code)
Three tools operate on the tree:
| Tool | What it does |
|---|---|
bin/skills-sync |
Plans and applies the symlink farm. Idempotent. --dry-run, --check (CI drift detection), --adopt (first migration, with backups), automatic rollback on failure. |
bin/skills-lint |
Validates the tree: manifest integrity, frontmatter contract, credential/PII scanning, portability hygiene. |
bin/skills-verify |
Confirms the symlink farm works end to end — static checks plus an optional live discovery probe against real client CLIs. |
- Python 3.11+
- PyYAML 6 (
python3 -m pip install -r requirements.txt) - Git
git clone https://github.com/VitorLudke/skillsync
cd skillsync
python3 -m pip install -r requirements.txt
bin/skills-lint # validate the tree (ships with one example skill)
bin/skills-sync --dry-run # preview the plan
bin/skills-sync # create the symlinks
bin/skills-verify --static # lint + drift check in one commandThen replace skills/hello-world/ with your own skills, update
skills-manifest.json, and rerun bin/skills-sync.
If a target already contains a real directory with the same name as a
canonical skill (a pre-existing copy), skills-sync refuses to touch it until
you rerun with --adopt, which moves the original to a timestamped backup
under ~/.local/share/skillsync/backups/ before linking.
Optional skillsync.toml at the repository root. Without it (or with it
untouched), the defaults are:
[sync]
# Directories that receive one symlink per canonical skill.
targets = ["~/.agents/skills", "~/.claude/skills"]
# Directories that must NOT contain entries named after canonical skills
# (some clients register duplicates when two discovery roots hold the same
# name). Canonical names found here are removed on sync; real files are
# backed up first when --adopt is passed.
cleanup = []Add a target for any agent that reads a skills directory:
[sync]
targets = ["~/.agents/skills", "~/.claude/skills", "~/.mycli/skills"]Agents that discover skills through their own config file instead of a
directory can usually just be pointed at this repository's skills/
directory directly — check your client's documentation for an
"additional skill directories" setting.
skills-sync also accepts ad-hoc extra targets: bin/skills-sync --target ~/.another/skills.
Canonical skills carry exactly two frontmatter keys:
---
name: my-skill
description: When and how to use this skill.
---name must match the directory name. Anything else is rejected by the lint —
downstream clients rewrite or inject other keys (version, author, tags),
so the canonical file stays at the intersection every client accepts.
bin/skills-lint # errors fail; warnings inform
bin/skills-lint --strict-warnings # for publishing: warnings also failErrors: broken manifest/frontmatter, symlinked resources inside the tree, credential material (AWS keys, API tokens, private keys, JWTs, database URLs with literal passwords, and more), client-specific invocations without a capability fallback. Warnings: email addresses, Brazilian document and phone formats (CNPJ, CPF, +55 numbers), oversized SKILL.md files.
A pre-commit hook is included — enable it with:
git config core.hooksPath .githooksbin/skills-verify runs the static checks, then (unless --static) installs
a uniquely named temporary probe skill, symlinks it into the configured
targets, asks real client CLIs whether they can see it, and removes every
path it created:
- Codex — probed when
~/.agents/skillsis a configured target (codex debug prompt-input). - Claude Code — probed when
~/.claude/skillsis a configured target (headlessclaude --printwith a small spend cap; requires an authenticatedclaudeCLI).
Targets that no built-in client reads are still probed for symlink integrity; if no built-in client applies, the live stage reports itself as skipped.
- Fail-closed validation: a broken manifest or skill aborts the plan before anything is touched.
- Every mutation is snapshot-verified against a preflight state and applied through quarantine-and-verify steps; a failure mid-apply rolls back the actions already taken.
--adoptnever deletes: replaced paths land in~/.local/share/skillsync/backups/<timestamp>/.- All roots must be dedicated directories strictly inside
HOME; targets may not overlap each other, the repository, or the backup area. - Exit codes are stable for CI:
0clean,1findings/drift,2usage or environment error.
Symlinks store absolute paths. If you move the canonical repository, rerun
bin/skills-sync from the new location — stale links pointing at the old
path are replaced in place.
Remove the symlinks by deleting them from your targets (they are plain
per-skill symlinks named after each skill), or delete the repo and run your
own cleanup; nothing else is installed anywhere. Adoption backups remain
under ~/.local/share/skillsync/backups/ until you delete them.
python3 -m unittest discover -s testsEverything runs against synthetic fixtures in temp directories; the suite never touches your real agent directories.
MIT