Sync your Claude Code "brain" — skills, agents, commands, plugins, MCP servers, memory, settings — across machines through your own Cloudflare R2, encrypted before it leaves your computer. No third-party server, no subscription.
A few small scripts on top of rclone. rclone's crypt backend
encrypts every file client-side with a passphrase and uploads only what changed. R2 only
ever stores scrambled blobs (filenames included) that Cloudflare can't read. Type the same
passphrase on another machine and it decrypts. Works on Windows, macOS, and Linux.
- rclone:
- Windows —
winget install Rclone.Rclone - macOS —
brew install rclone - Linux —
sudo apt install rclone(orcurl https://rclone.org/install.sh | sudo bashfor the latest)
- Windows —
- A Cloudflare R2 bucket + an API token (Dashboard → R2 → Manage API Tokens)
- Node.js (already ships with Claude Code) — used for the MCP + plugin steps
The scripts: setup (configure R2 once), claude-push / claude-pull (sync), and
claude-restore (recover a file from a backup). Each ships as a .sh (macOS/Linux/Git Bash)
and a .ps1 (Windows PowerShell).
You store everything in your own Cloudflare R2 (free tier is plenty — the synced config is only a few MB).
- Go to Cloudflare Dashboard → R2 Object Storage (enable R2 the first time).
- Click Create bucket → name it
claude-sync. - Go to Manage R2 API Tokens → Create API Token.
- Select Object Read & Write permission → Create.
- Copy the values you'll paste into
setup: Account ID, Access Key ID, and Secret Access Key (the secret is shown only once).
Clone it once on each machine:
git clone https://github.com/markee2301/claude-code-sync.git
cd claude-code-syncRun the scripts with
bash(macOS/Linux/Git Bash) or the.ps1via-File(Windows PowerShell). Don't double-click them or type./script.shin PowerShell — Windows tries to open the file (app picker / text editor) instead of running it.
bash setup.sh # one-time: enter R2 Account ID, Access Key, Secret (hidden), bucket
bash claude-push.sh # this machine -> encrypt -> R2
bash claude-pull.sh # R2 -> decrypt -> this machinepowershell -NoProfile -ExecutionPolicy Bypass -File .\setup.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\claude-push.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File .\claude-pull.ps1bash setup.sh
bash claude-push.sh
bash claude-pull.shsetup writes a gitignored rclone.conf — your secret key and passphrase never touch
git. push and pull each prompt for your encryption passphrase (the one thing to
remember — never stored). Use the same passphrase on every machine.
push asks for the passphrase twice (typo guard) and writes a small encrypted
canary file. pull decrypts that canary before downloading anything, so a wrong
passphrase is caught immediately and re-prompted instead of pulling undecryptable data.
pushmirrors your machine to R2 (so push from whichever machine is up to date);pullbrings changes down. Following this order keeps every machine current and avoids overwriting newer work. See How push and pull differ for the details and the built-in backup safety net.
Everything is decided by claude-filter.txt — edit it to change these defaults.
Path (under ~/.claude) |
What it is |
|---|---|
settings.json |
Shared settings: enabled plugins, statusline, marketplaces |
CLAUDE.md |
Global instructions (if you keep one) |
MEMORY.md + memory/ |
Saved memory and its index |
skills/ |
Your custom skills (symlinked skills are dereferenced into real files) |
agents/ |
Your custom subagents |
commands/ |
Your custom slash commands |
rules/ |
Custom rules (if present) |
workflows/ |
Custom workflows (if present) |
plans/ |
Plan-mode plans |
tasks/ |
Task-tracking state |
plugins-manifest.json |
Path-free list of your plugins + marketplaces, generated on push |
mcp-servers.json |
Your local MCP servers, snapshotted from ~/.claude.json on push |
| Path | Why not |
|---|---|
plugins/ (whole dir) |
installed_plugins.json / known_marketplaces.json store absolute OS-specific install paths, and cache/ holds thousands of arch-specific files (node_modules, venvs). Plugins are reinstalled from plugins-manifest.json instead. |
projects/ |
Conversation history. Large, and folders are keyed to absolute project paths that differ per machine (so they wouldn't resume cleanly anyway). Off by default. |
sessions/ |
Live session/runtime state — machine-local. |
history.jsonl |
Prompt-input history. Off by default (flip on if you want it). |
.credentials.json |
Your auth token — a secret. Run /login on each machine instead. |
settings.local.json |
Per-machine settings/permissions (that's the file's whole purpose). |
shell-snapshots/, ide/, file-history/ |
Machine/OS-specific runtime state (e.g. Windows shell snapshots). |
cache/, image-cache/, paste-cache/, downloads/, backups/, *.log |
Regenerated caches and local noise. |
Want conversation history too? In
claude-filter.txt, change- /projects/**and- /history.jsonlto+. Note: those transcripts are keyed to absolute paths, so they won't auto-resume on a machine with a different home dir or OS (see Limitations below).
Plugins are restored on pull by reinstalling from the synced manifest
(claude plugin marketplace add + claude plugin install), so their hardcoded install
paths don't have to be portable. Login/auth is not synced — run /login on the new machine.
- push = mirror. R2 is made to match this machine. Deleting a synced file locally and
pushing removes it from R2 too. So push from the machine that's up to date. Replaced
and deleted blobs are kept under
backups/<timestamp>/in R2, so a bad push is recoverable. - pull = additive. Brings R2 down without deleting anything local (uses
copy).
Rule of thumb: pull before you work, push when you're done.
Every push runs with --backup-dir, so the mirror can't lose data silently:
rclone sync ~/.claude r2crypt:vault --backup-dir r2crypt:backups/<timestamp>Before overwriting a changed file or deleting one that's gone locally, rclone moves the
old version into backups/<timestamp>/ instead of destroying it. Each push that changes
something creates one timestamped folder containing only the files it replaced or removed
(a delta, not a full snapshot). Pushes that add only new files create no backup. Backups are
encrypted like everything else.
Do I need to do anything? Almost never. Backups are insurance you can ignore until you
actually notice something lost or reverted — a skill, command, agent, or memory file that
you know you had is suddenly missing or back to an older version. That has one cause: a
push from a machine that wasn't up to date overwrote the newer state in R2 (exactly
what the pull-before-work rule prevents). Two ways you'd catch it:
- The push output reports it deleted files you didn't expect (e.g.
Deleted: 12). - After a pull, something you rely on is gone or stale.
The "…recoverable at backups/…" line printed on every push is informational, not an alert
— ignore it unless you've actually lost something.
Easiest — run the interactive helper, which lists every backup snapshot, lets you pick
one, shows what's inside, and restores a file or folder (to a safe ./restored/ folder by
default, or straight into ~/.claude):
bash claude-restore.sh # macOS / Linux / Git Bash
# Windows PowerShell:
# powershell -NoProfile -ExecutionPolicy Bypass -File .\claude-restore.ps1Prefer doing it by hand? The same thing with raw rclone, from your claude-code-sync folder:
export RCLONE_CONFIG="$PWD/rclone.conf"
read -rsp "passphrase: " P; export RCLONE_CONFIG_R2CRYPT_PASSWORD=$(rclone obscure "$P"); unset P; echo
rclone lsf r2crypt:backups/ # list snapshots (one per changed push)
rclone tree r2crypt:backups/<timestamp> # see what's inside one
rclone copy "r2crypt:backups/<timestamp>/skills/foo/SKILL.md" ./restored/ # pull a file backThere's no "restore everything to date X" command — backups are deltas, so recovery is a deliberate cherry-pick.
Backups accumulate (one folder per changed push). To prune old ones:
rclone purge r2crypt:backups/<old-timestamp>- Conversation history doesn't auto-resume across machines. Claude indexes sessions by
absolute project path (
~/.claude/projects/-Users-alice-my-app/). A different username, home dir, or OS produces a different key, soclaude --resumeon machine B won't find machine A's threads even if you syncprojects/. Settings, skills, agents, commands, memory, and plugins are all path-independent and sync fine — this caveat is only about resuming old conversations. (A future version could rewrite paths to a portable token on sync; not implemented yet.)
- Encryption happens locally (rclone
crypt); R2 stores only encrypted blobs. - The passphrase is never written to disk or uploaded. Lose it = no recovery, by design.
rclone.conf(your R2 keys) is gitignored. Anyone with it can reach your bucket, but the contents stay encrypted by your passphrase. Treat it like a password.
Configured for Cloudflare R2. rclone also speaks AWS S3, Backblaze B2, etc. — edit the
[r2] remote in rclone.conf for another provider; the crypt layer stays the same.