The first time I tried to share a Claude Code skill with the rest of my engineering team, I almost wiped out my own sessions, credentials, and a folder of WIP projects in the process. I didn't, but only because I stopped a stow command halfway through and realised what I was about to do.
The setup looked harmless. We had a repo, agents-shared, where teammates were starting to drop their own slash commands and skills — Marc had his document and implement prompts in a personal folder, Lena had a TDD skill, I had a half-finished one for our analytics dashboard. Everyone was reinventing slightly different versions of the same thing. The obvious move: make the repo the source of truth, symlink it into everyone's ~/.claude/, done.
It is not done. ~/.claude/ is not a config folder you can symlink the way you'd symlink a .vimrc. It is a mixed-state directory where team-shared assets live next to deeply personal, machine-local state — and most of the obvious sharing strategies treat the whole folder as one thing. This post is the field-notes from doing it properly, and the rule the experience left me with.
The folder that isn't actually a config folder
If you run ls -la ~/.claude after a couple of weeks of using Claude Code, you get something like this:
backups/
cache/
claude_desktop_config.json
debug/
history.jsonl
ide/
mcp-needs-auth-cache.json
plans/
plugins/
policy-limits.json
projects/
session-env/
sessions/
settings.json
shell-snapshots/
skills/
statsig/
telemetry/
todos/
CLAUDE.md
commands/
agents/
Only the bottom four entries — skills/, commands/, agents/, and CLAUDE.md — are the things a team might want to share. Everything above is local state: open sessions, your conversation history, the projects you've worked on, the auth tokens for your MCP servers, your settings overrides, your shell snapshots, your in-flight plans.
The naïve symlink strategies fail in different ways:
- Symlink the whole
~/.claudeto the repo. Now Claude Code writes its sessions and credentials into the repo. You either commit your auth tokens by accident, or your.gitignoregrows into an angry monster trying to chase down every file the tool decides to create. - Symlink only
~/.claude/skills,commands, etc. Closer, but if you ever runstowwith the wrong package layout — or with the wrong flag — it will helpfully replace the entire~/.claude/directory with a single directory symlink. That breaks Claude Code's ability to write its own state and quietly disconnects your sessions. - Copy files in and out with a Make target. Works for one person. Drifts immediately the moment anyone edits the local copy and forgets to copy it back.
The right framing is: ~/.claude/ is two folders pretending to be one. There's a small team-shared surface — four named entries — and a much larger machine-local surface that the tool owns and that must never travel.
The rule you actually need
Once you frame it that way, the implementation falls out. We landed on GNU stow with three non-obvious constraints:
- The stow package layout mirrors
~/.claude/exactly, but only contains the four shared entries:
The deny-by-defaultconfigs/claude/.claude/ ├── .gitignore # deny-by-default allowlist ├── CLAUDE.md ├── agents/ ├── commands/ └── skills/.gitignoreinside the package is the safety net: anything new Claude Code writes here will not be tracked unless explicitly re-included. Sessions can't leak through, even if someone runs a future Claude Code session pointing into this directory by mistake. - Stow runs with
--no-folding. By default stow does "tree folding" — if a target directory doesn't already exist, stow creates a single symlink for the whole folder. That's exactly the failure mode we wanted to prevent.--no-foldingforces stow to create one symlink per leaf, so~/.claude/stays a real directory that Claude Code can write into. - Conflicts are resolved per-item, not per-folder. If
~/.claude/skills/already exists as a real folder with personal skills, we move that one folder to~/.claude/skills.backup-<timestamp>and let stow create the symlink. We never touch~/.claude/itself. The whole-folder backup is the most dangerous wrong default in this kind of tool, and it's the one most stow tutorials show first.
The whole sync command — install stow, locate the repo, git pull --ff-only, inventory the four tracked items, offer per-item migration, offer per-item backup, dry-run, then stow for real — is a single Claude Code slash command checked into the repo. The first time you run it, it asks for confirmation at every destructive step. Subsequent runs are git pull plus stow --restow and produce no prompts at all.
The bootstrap problem, and how to test it without fear
There is one moment in this kind of system where you can do real damage: the first time you run the sync command on your own ~/.claude/. By definition you can't unit-test it against the live folder without risking the folder you're trying to test against.
The trick is a fake $HOME:
mkdir -p /tmp/fake-home/.claude
echo '{"theme":"dark"}' > /tmp/fake-home/.claude/settings.json
echo '{"creds":"fake"}' > /tmp/fake-home/.claude/.credentials.json
cd ~/Code/agents-shared
stow --no-folding --target=/tmp/fake-home --dir=configs \
--verbose=2 --simulate claude
That dry run, against a sandbox that you can rm -rf with no consequences, tells you exactly which symlinks would be created and — more importantly — which files would not be touched. If settings.json or .credentials.json show up anywhere in the output, the config is wrong and the real run would have damaged your real folder. The fake home is the cheapest safety harness I've added to anything in years.
The dry run also caught a bug I'd never have spotted otherwise: a stale empty directory (configs/claude/skills/, left over from an earlier layout) was making stow plan MKDIR: skills in the target root. Against the fake home that's a /tmp/fake-home/skills/ with no consequence. Against the real home it would have created ~/skills/ in my actual home directory, with mystery contents and no obvious origin. rm -rf the empty repo folder, re-run the dry run, problem gone — but I'd have spent a confused hour the next morning if I'd skipped the sandbox step.
Building the tool with the tool it's going to sync
The other twist worth flagging: I built the /sync-agents slash command in a Claude Code session, with Claude Code, while my ~/.claude/ was still wired up to an older, broken layout that the new command was meant to fix. The command lives in the repo at configs/claude/.claude/commands/sync-agents.md, and the only way to make it appear as /sync-agents in my own Claude Code is to run itself.
The bootstrap goes: copy the file in manually once (cp configs/claude/.claude/commands/sync-agents.md ~/.claude/commands/), restart Claude Code, run /sync-agents, accept the backup of the existing ~/.claude/commands/ folder, and let stow create the symlink that now manages itself. From the second run onwards, editing the prompt in the repo is enough — stow --restow picks up the new content on the next sync.
It's a small thing, but it puts a constraint on the design that's worth naming: a config-sync tool has to be installable without being installed. Every other dependency can assume the tool exists; the first run cannot. That's true for any tool that manages its own deployment, and it usually shows up too late, after you've already added a step that quietly assumes the tool is already on the path.
What I still don't have a clean answer for
Two things are still messy.
The first is multi-tool sharing. The same prompt might be useful as a Claude Code slash command, a Codex skill, an OpenCode command, and a GitHub Copilot prompt — all four tools have different folder conventions, different frontmatter, different invocation patterns. We tried keeping a tool-agnostic prompts/ folder with symlinks fanning out into each tool's package, and rolled it back the same day in favour of duplication. The indirection bought us less than it cost, and the duplication problem doesn't actually exist yet because the prompts are still settling. We'll revisit when (and only if) the same prompt drifts between tools and the divergence hurts.
The second is personal skills that aren't ready to share yet. Right now I have a weekly-insights skill I'm iterating on in ~/.claude/skills/; the moment I run /sync-agents, that folder gets backed up and replaced with a symlink to the repo, where my skill doesn't live. The current answer is "back it up, develop in the repo on a branch, symlink the branch in manually for testing, PR when ready" — but the seam between "personal scratch" and "team-shared" is where I expect the friction to accumulate as more people start using this. A future version of the command might handle a ~/.claude/skills.local/ overlay merged into the symlinked folder; I haven't built it because I want to see what actually breaks first.
If you take one thing
The mistake to avoid is treating ~/.claude/ — or any modern AI tool's config directory — as a single thing you can put under version control. It isn't. It's a small set of team-shared files surrounded by a much larger set of machine-local state that the tool owns and writes to constantly. Once you accept that and design around it, the rest is mechanical: a deny-by-default allowlist on the package side, --no-folding on the stow side, per-item backups instead of whole-folder rescues, and a fake $HOME dry-run before anything destructive runs against your real one.
The same pattern will apply to whatever comes after Claude Code, and after the version of ~/.claude/ that exists today. Pick the four files. Leave the rest alone.