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 stared at what was about to happen.
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, 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 ~/.claude to the repo and now Claude Code writes its sessions and credentials into the repo — you commit auth tokens by accident, or your .gitignore grows into an angry monster chasing every file the tool decides to create. Symlink only the four subfolders and you're closer, but if you ever run stow with the wrong package layout it will helpfully replace the entire ~/.claude/ directory with a single directory symlink, quietly breaking Claude Code's ability to write its own state. Copy files in and out with a Make target works for one person and drifts 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. I 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 a future Claude Code session points into this directory by mistake. - Stow runs with
--no-folding. By default stow does "tree folding" — if a target directory doesn't already exist, it creates a single symlink for the whole folder. That's the exact failure mode I 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, I move that one folder to~/.claude/skills.backup-<timestamp>and let stow create the symlink. I 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 run 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
Back to the moment I stopped the stow command halfway through. The reason I stopped is exactly the same reason this kind of tool is dangerous: the first time you run it, you cannot unit-test it against the live folder without risking the folder you're trying to test against. By definition.
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 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.
One small constraint worth naming because it usually shows up too late. The sync command lives in configs/claude/.claude/commands/sync-agents.md, and the only way to make it appear as /sync-agents in Claude Code is to run itself. That means the first run has to be a manual cp from the repo into ~/.claude/commands/; from the second run onwards the symlink takes over. A config-sync tool has to be installable without being installed, and the bootstrap step is the place that constraint shows up. It is true for any tool that manages its own deployment, and it usually goes wrong the first time someone forgets it.
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. I 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 less than it cost, and the duplication problem doesn't actually exist yet because the prompts are still settling. I'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.