Sync Claude Code sessions between two machines over SSH — no cloud.
Project description
Sync Claude Code sessions between two machines over SSH — no cloud storage,
just rsync between your own boxes. Start a session on your Linux desktop,
claude --resume it on your Mac (or the other way round).
Quickstart (30 seconds)
uv tool install claude-hop # or: pipx install claude-hop
claude-hop init # asks for the SSH host, detects the rest
claude-hop push # then `claude --resume` on the other machine
No uv? One-liner that installs it first:
curl -sSL https://raw.githubusercontent.com/SeiduTakizawa/Claude-hop/main/install.sh | bash
Requirements: Python ≥ 3.11 on this machine; sshd and rsync on the other
one (key-based SSH auth recommended).
Supported platforms
Any pair of Unix-shaped machines, in either direction: Linux ↔ macOS,
Linux ↔ Linux, macOS ↔ macOS. The remap isn't tied to /home vs
/Users — it maps your home here to your home there, whatever they are
(identical homes on both sides work too; the remap just becomes a no-op and
you keep the merge/safety machinery).
Windows is supported via WSL: inside WSL you have a real Linux home,
rsync, and sshd, so claude-hop works there like on any Linux box — it
syncs the Claude Code you run inside WSL. Native (non-WSL) Windows is not
supported: Claude Code encodes C:\ project paths differently and there's
no native rsync. If you need it, open an issue.
The problem it solves
Claude Code stores sessions in ~/.claude/projects/, indexed by the
absolute path of the project with every non-alphanumeric character
replaced by a dash:
/home/alice/work/webshop → ~/.claude/projects/-home-alice-work-webshop/
The absolute paths also appear inside the session JSONL files — cwd
fields, file paths in tool calls. So copying the files verbatim from a Linux
box (/home/alice) to a Mac (/Users/alice) produces sessions that
claude --resume can never find: the directory name encodes the wrong path
and the transcript still references the wrong filesystem.
claude-hop rewrites both during sync — directory names and JSONL
contents — mapping your local home to the remote home (auto-detected at
init), in whichever direction you sync. The rewrite is boundary-aware
(/home/al never matches inside /home/alice) and round-trip safe: push
then pull reproduces your files byte for byte.
Commands
| Command | What it does |
|---|---|
claude-hop init |
Interactive setup: SSH host, connection test, remote home auto-detection, rsync checks. |
claude-hop status |
Table of local projects, session counts, and the remapped name each gets on the remote; flags ambiguous mappings. |
claude-hop diff |
What would sync, in both directions, without writing anything. |
claude-hop push |
Send local sessions to the remote machine. |
claude-hop pull |
Fetch remote sessions and merge them in. |
claude-hop doctor |
Environment checks: SSH, rsync versions, config, running Claude Code, stale staging dirs. |
push and pull accept --dry-run (-n), --yes (-y), and --force.
Safety model
- Merge, never clobber. rsync runs with
-u(newer file wins) and never with--delete— a sync can only add or update sessions, never remove. - Staging, not in-place. Remapping happens in a temp dir;
~/.claude/projects/is only ever touched by the merge itself. - Refuses to run alongside Claude Code. Sessions are flushed to disk on
exit, so syncing mid-session would miss your latest work. Override with
--forceif you know what you're doing. - First-pull backup. The first pull that would merge into existing local
sessions offers a tarball backup (
~/.claude/backups/) before touching anything.
Configuration
~/.config/claude-hop/config.toml (written by init, $XDG_CONFIG_HOME
honoured):
[remote]
host = "mac.local" # SSH alias or hostname; "" = a local directory
home = "/Users/alice" # auto-detected at init
[sync]
include_history = false # also sync ~/.claude/history.jsonl (remapped)
include_agents = false # also sync ~/.claude/agents/ (verbatim)
include_skills = false # also sync ~/.claude/skills/ (verbatim)
[mappings]
# For projects that live at *different relative paths* on the two machines.
# Specific mappings win over the generic home remap.
"/home/alice/work/webshop" = "/Users/alice/projects/webshop"
claude-hop status shows exactly how every project will map; if one looks
wrong, add it under [mappings].
Limitations worth knowing
- Sessions whose conversation text literally mentions the other machine's
home path (e.g. a transcript discussing
/Users/alice/...while on the Linux box) can't be rewritten losslessly — path rewriting is text-level by design, so such mentions get remapped too. - Session files that aren't valid UTF-8 are copied verbatim without remapping rather than risk corrupting them.
- On macOS 15+, the bundled
rsyncisopenrsync; it works, but if you hit option errors,brew install rsync.
Development
uv sync # install with dev dependencies
uv run pytest # unit + integration tests (integration needs rsync)
uv run ruff check .
make e2e # full two-container SSH round trip (needs Docker)
The e2e rig (e2e/) spins up two containers — one with a Linux-style home,
one macOS-style — wires SSH key auth between them, and drives the real CLI
through push, modify-on-the-other-side, and pull.
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_hop-0.1.0.tar.gz.
File metadata
- Download URL: claude_hop-0.1.0.tar.gz
- Upload date:
- Size: 1.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ffc3dda2788196dadef92b7692bc54f836de9eb978854b0dbc2038704362af1
|
|
| MD5 |
4307b2e1b73979a8fa4a173470559453
|
|
| BLAKE2b-256 |
b41bbe3ef6265781dbd766fc34ec4ced5f56808bf16fe4897c00ed79539c9934
|
File details
Details for the file claude_hop-0.1.0-py3-none-any.whl.
File metadata
- Download URL: claude_hop-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.27 {"installer":{"name":"uv","version":"0.11.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f422fb7fa2a0d452efa3ed94cde11d8834e90a8d8e0a8ccfd1c2b69a262ec970
|
|
| MD5 |
53acca96d8744b3e2103ae0f0f563c7c
|
|
| BLAKE2b-256 |
79269e13c1a17f7be46b8a006f14749114fbf882cfb6ce02d1c01dfd123e12f8
|