A starter template for personal cross-container sync of Claude Code config:
identity, preferences, plans, project memories, user-level skills, slash
commands, and agents. Each container symlinks selected paths under
~/.claude/ into a per-user fork of this repo's working tree, and Claude
Code hooks auto-pull on session start and auto-push on session stop.
This starter is intentionally public so you can read and audit it. Fork it to a PRIVATE repo of your own before populating it with anything sensitive. The installer enforces that the configured repo is private and will refuse to run otherwise.
~/.claude/... path |
~/claude-shared/... target |
Notes |
|---|---|---|
CLAUDE.md |
CLAUDE.md |
Identity, plan rules, no-secrets rule |
settings.json |
settings.shared.json |
Portable bits only |
plans/ |
plans/ |
Flat layout; descriptive names |
skills/, commands/, agents/ |
same | User-global only |
projects/<slug>/memory/ |
same | Optional, opt-in via PROJECT_DIRS |
Not synced (per-container/local only): .credentials.json, sessions,
shell snapshots, history, cache, file-history, backups, mcp auth cache,
settings.local.json (your per-container overrides), .commit-msg-pending,
and anything else matching .gitignore.
- Fork this repo to your own GitHub account (e.g.
github.com/YOU/claude-shared). - Make your fork private —
gh repo edit YOU/claude-shared --visibility private. - Edit
install.shand setGH_OWNERto your username (or exportCLAUDE_SHARED_OWNER=YOUwhenever you run it). - Optionally edit
PROJECT_DIRSininstall.shto opt into per-project memory sync for projects that live at the same absolute path on every container you use.
Inside the running container, after docker run:
# bootstrap.sh installs gh, runs gh auth login, runs claude auth login.
# It is meant to be vendored into your Dockerfile project (see the header
# in the script).
bash /path/to/bootstrap.sh
# Or do the steps manually:
sudo apt-get update && sudo apt-get install -y gh # per github cli install docs
gh auth login && gh auth setup-git # one-time per container
claude auth login # one-time per container
# Then clone your fork + install (replace YOU with your username):
git clone https://github.com/YOU/claude-shared.git ~/claude-shared
CLAUDE_SHARED_OWNER=YOU ~/claude-shared/install.shbootstrap.sh is idempotent (each step short-circuits if already done) and
keeps the auth-setup steps version-controlled in one canonical place.
The manual flow also works in foreign environments (any Linux box with gh,
git, jq, and claude installed). install.sh must run inside a
running container because it gates on gh auth status.
SessionStarthook runshooks/pull.sh:git pull --rebaseunder aflock, with atimeout. Never blocks a session; logs to.sync.log.Stophook runshooks/push.sh: stages changes, runshooks/scan-secrets.sh, commits with a generic identity (claude-sync@local, no hostname, no session id), pushes. Retries once on push failure viapull --rebase. On rebase conflict, parks the commit onconflict/<random-id>-<timestamp>and surfaces the branch to the user.- Descriptive commit messages. Write a subject (or full message) to
.commit-msg-pendingbefore your turn ends; the Stop hook uses it and deletes the file after a successful commit. Falls back tosync <TIMESTAMP>if no pending message exists. The file is gitignored. - Statusline reads the latest line of
.sync.logand renders it persistently at the bottom of the terminal, so the most recent pull/push result (e.g.push: pushed 3 file(s)orpull: ok (up to date)) is always visible without leaving the session.
Your fork must remain private. install.sh aborts if it detects public
visibility. The pre-commit secret scan in hooks/scan-secrets.sh greps the
staged diff for AWS keys, GitHub/Slack tokens, JWTs, private-key headers,
and sensitive-word-adjacent high-entropy assignments. Content discipline is
documented in CLAUDE.md as a standing instruction that rides along to every
session.
/plansslash command (commands/plans.md+commands/plans-list.sh): lists everything in~/.claude/plans/, sorted oldest-first, with summaries from each plan's first heading, word-wrapped to your terminal width. Detects the real terminal width by walking up the parent process tree to find a tty, since slash-command subshells have no tty of their own.- Status line (
hooks/statusline.sh): surfaces the most recent meaningful line from.sync.log(pushed, blocked, conflict, …) rather than the boringno changesno-op that the Stop hook writes most turns, and appends(idle)when nothing interesting has happened since — so you can still tell the sync hooks are alive. Wired up viastatusLineinsettings.shared.json.