claude-remote-sync
v0.2.1
Published
Run Claude Code on a trusted remote machine, kept in sync with your control machine via Mutagen.
Maintainers
Readme
claude-remote-sync
Command name: the CLI is invoked as
claude-remote-sync, matching the npm package name — that's the command every example below uses.claude-remote(no-sync) still works today as a legacy alias so existing installs keep working unchanged, but it's deprecated and will be removed in a future release. New setups should useclaude-remote-sync.
What this is
claude-remote-sync runs Claude Code (with --dangerously-skip-permissions)
on a separate, fully-trusted machine — instead of on your own main machine —
while making that remote session feel local. It keeps two things
continuously synced between your control machine and the remote machine via
Mutagen:
- Claude Code's own config and session history (
~/.claude) - Your active project's files
Then it drops you into a live Claude Code session on the remote over SSH + tmux, working on the synced copy of your project.
Why: --dangerously-skip-permissions gives Claude Code unrestricted
filesystem/shell access with no confirmation prompts — safer to run against a
separate machine's filesystem than the one you actually care about protecting.
Continuous two-way sync is what makes that separate machine still feel like
your own dev environment: same project files, same Claude session history,
resumable from either side.
The remote machine doesn't need to be on the same local network as your control machine — any host you can SSH into works (LAN, a machine reachable over the internet, or one behind a VPN/Tailscale). See "How it works" below for what's actually required.
How it works
Control machine Remote (Linux / WSL2)
------------------------ ------------------------
~/.claude --- Mutagen sync ---> <homeMirrorPath>/.claude
(config + history) "claude-home"
(started once by
`claude-remote-sync setup`,
runs permanently)
<active project> --- Mutagen sync ---> <same absolute path>
"workspace"
(retargeted on every
`claude-remote-sync launch`)
`claude-remote-sync launch` --- SSH + tmux ---> Claude Code running
attaches here inside the workspaceTwo Mutagen sync sessions run independently of each other:
claude-home— mirrors~/.claude(Claude Code's config and session history) between the control machine and<homeMirrorPath>/.claudeon the remote. Created once byclaude-remote-sync setupand left running in the background from then on — it isn't tied to any particular project.workspace— mirrors your active project directory. Retargeted every time you runclaude-remote-sync launch, either against the project set inconfig.yamlor a different one passed viaCLAUDE_REMOTE_WORKSPACE.
Both sessions sync two-way. If the same file changes on both sides before a sync catches up, the remote's copy always wins — see "Operational rules" below for what that means in practice.
On top of sync, claude-remote-sync launch opens an SSH connection to the
remote and attaches to a tmux session there, running Claude Code inside the
synced workspace. Detaching (Ctrl-b d) leaves that tmux session — and
Claude Code — running on the remote; running claude-remote-sync launch
again reattaches to it instead of starting a new one.
Why paths have to line up: homeMirrorPath in config.yaml is set to
mirror the control machine's own home directory path (e.g. /Users/pak), and
your project lives at the same absolute path on both sides. This is what lets
Claude Code resume a session that has history from the other machine — its
own path-derived session keys only match up because the paths are identical,
not just similarly structured.
Components
| Component | Runs on | Responsibility |
|---|---|---|
| claude-remote-sync CLI (aka claude-remote, deprecated) | Control machine | Everything you run — setup, launch, status, monitor, config. |
| config.yaml | Control machine | Single source of truth: remote host/user/OS, path layout, sync ignore list, tmux session name, launch behavior. |
| Mutagen | Control machine (daemon) + Remote (agent, auto-installed by Mutagen itself) | Owns both sync sessions described above. |
| tmux, Node.js (via nvm), Claude Code CLI | Remote | Installed automatically by claude-remote-sync setup — nothing to install by hand beyond SSH access. |
Prerequisites
On the control machine (whatever machine you don't want
--dangerously-skip-permissions running against directly):
- Node.js >= 18 (to install/run the
claude-remote-syncCLI itself) - Mutagen installed (e.g.
brew install mutagen-io/mutagen/mutagenon macOS — see mutagen.io/documentation/introduction/installation for other platforms) - SSH key access to the remote already working —
ssh <user>@<host>should need no password or prompt. This tool never generates or copies keys for you.
On the remote machine:
- Linux (any apt-based distro) or Windows with WSL2 already installed (native Windows without WSL2 is not supported).
- Reachable over SSH from the control machine.
- On Windows specifically: the machine's SSH server must be configured so an
incoming SSH connection lands inside WSL2, not native PowerShell/cmd.exe
— the default OpenSSH Server on Windows drops you into PowerShell unless
it's been set up to hand sessions to the Linux userspace.
claude-remote-sync setupassumes this is already true; it does not check or configure it, and every remote command it runs expects a bash/Linux shell. Step-by-step:docs/remote-setup/windows-wsl2.md(Windows) ordocs/remote-setup/linux.md(Linux). tmux, Node.js, and the Claude Code CLI are not manual prerequisites —claude-remote-sync setupinstalls all three.
Setup
Install:
npm install -g claude-remote-syncThis installs the claude-remote-sync command (and, for backward
compatibility, the deprecated claude-remote alias).
Create ~/.config/claude-remote/config.yaml (the config directory name is
unchanged from before the claude-remote-sync command name existed, so
upgrading an existing install doesn't move or lose your config):
mkdir -p ~/.config/claude-remotewith these fields:
remote:
host: 192.168.1.50 # or a public hostname/IP — LAN is not required
user: pak
sshKeyPath: ~/.ssh/id_ed25519 # optional — falls back to ssh-agent/~/.ssh/config
os: linux # or windows-wsl2 — checked against `uname -a` during setup
homeMirrorPath: /Users/pak # must match `echo $HOME` on THIS control machine
workspace:
local: /path/to/your/project
sync:
ignore: [node_modules, .venv, dist, build, __pycache__]
tmux:
sessionName: claude-remote
launch:
autoStartClaude: true
claudeArgs: ["--dangerously-skip-permissions"]Then run the one-time setup, which verifies SSH access and installs
everything needed on the remote (tmux, Node.js, the Claude Code CLI), and
starts the claude-home sync session:
claude-remote-sync setupDaily use
claude-remote-sync launch
# or, to work on a different project without editing config.yaml:
CLAUDE_REMOTE_WORKSPACE=/path/to/other/repo claude-remote-sync launchlaunch syncs the active workspace and drops you into a live Claude Code
session on the remote, inside a tmux session. Detach with Ctrl-b d any
time; running claude-remote-sync launch again reattaches to that same
session instead of starting a new one.
If your workspace root is itself a folder of unrelated projects (e.g.
CLAUDE_REMOTE_WORKSPACE=/Users/you/Projects, containing Deepsel/,
Cookie/, ...), launch prompts you to pick which immediate subfolder
Claude Code should actually cd into and start from — without narrowing
what gets synced, which always stays the full workspace root. Picking a
folder gets its own tmux session (so switching folders across launches
never leaves you stuck in a stale cd), and Claude Code picks up that
folder's own CLAUDE.md/.claude/ config and session history instead of
the root's. The prompt is skipped (defaulting to the root) whenever
CLAUDE_REMOTE_WORKSPACE is set, --yes is passed, or the workspace has
no subfolders to choose from — use --folder <name> (or --folder root)
to skip it explicitly and pick non-interactively.
Check sync/connectivity state without launching: claude-remote-sync status.
Command reference
claude-remote (no -sync) is a deprecated alias for every command below —
identical behavior, kept only so existing installs don't break. Use
claude-remote-sync.
--config <path>(global option, before the subcommand) — use a config file other than the default~/.config/claude-remote/config.yaml.claude-remote-sync setup— one-time: verify SSH access, install remote dependencies, start the~/.claudesync session.claude-remote-sync launch [-y|--yes] [-f|--folder <name>]— sync the active workspace and drop into a live Claude Code session on the remote.-y/--yesskips the concurrent-session confirmation prompt described below (see "Operational rules") — useful for scripting, but skips a real safety check, so don't reach for it out of habit.-f/--folder <name>launches directly in that immediate subfolder of the workspace root (orrootfor the root itself), skipping the interactive folder picker.claude-remote-sync status— show SSH connectivity and both sync sessions' state.claude-remote-sync monitor [--interval <seconds>]— live-stream a combined dashboard: both sessions' Mutagen sync status plus CPU/RAM/disk performance for the control machine and the remote, refreshed every--intervalseconds (default3).Ctrl+Cstops watching, not the sync itself — it keeps running in Mutagen's background daemon either way.claude-remote-sync config— print the fully resolved config (after anyCLAUDE_REMOTE_WORKSPACEoverride) as JSON; useful for confirming which workspace/paths a command would actually use before running it.
Operational rules (read before your first real session)
- Never run Claude Code on both the control machine and the remote at the
same time.
~/.claudeis synced continuously, not instantly — running both sides at once can clobber the control machine's session/memory state (the remote side wins on conflict).launchprompts you to confirm this before every session; don't reflexively pass--yesunless you've actually checked. - Avoid git write operations (commit, checkout, merge) on both sides at
the same time, for the same reason — both sides have a live,
bidirectionally-synced
.gitdirectory. - Conflicts, if they happen, default to "remote wins". Check
claude-remote-sync statusif something looks like it reverted unexpectedly.
Publishing
Published to npm as claude-remote-sync. Releases are built and published
by .github/workflows/publish.yml, triggered by pushing a vX.Y.Z tag —
nothing is ever published from a local machine.
One-time setup (do this once, before the first release):
npm loginlocally and runnpm publish --access publiconce by hand. npm's Trusted Publisher setting (used for every release after this) lives on a package's settings page, which only exists once the package has been published at least once.- On npmjs.com: package page → Settings → Publishing access → add a
Trusted Publisher — GitHub Actions, repo
anhkhuong975/claude-remote, workflow filepublish.yml, no environment. - From then on, CI publishes via OIDC (no
NPM_TOKENsecret needed, per theid-token: writepermission in the workflow).
Every release after that:
npm version patch # or minor / major — bumps package.json + creates a git tag
git push --follow-tagsThe workflow verifies the pushed tag matches package.json's version,
builds, and publishes with provenance.
Manual end-to-end verification checklist
This project has no automated tests and was implemented without a reachable SSH target available during initial development. The following still needs a real run against an actual remote machine before trusting this day-to-day:
- [ ] After a fresh
npm install -g claude-remote-sync: bothwhich claude-remote-syncandwhich claude-remoteresolve, andclaude-remote --help/claude-remote-sync --helpprint identical output (the deprecated alias behaves identically to the primary command). - [ ]
claude-remote-sync setupagainst a real, freshly-provisioned Linux or WSL2 machine: completes without error, leavestmux,node, andclaudeinstalled, andmutagen sync list claude-remote-claude-homeshows the session asWatching for changes. - [ ] Edit a file inside
~/.claude/projects/on the control machine; confirm it appears on the remote within a few seconds (and vice versa). - [ ]
claude-remote-sync launch: confirm it attaches to the correct tmux session, in the correct directory, withCLAUDE_CONFIG_DIRset correctly (echo $CLAUDE_CONFIG_DIRinside the session), and that Claude Code can resume a session that has history from the control machine. - [ ] Detach (
Ctrl-b d), runclaude-remote-sync launchagain: confirm it reattaches to the same tmux session instead of creating a new one. - [ ] Against a workspace root with subfolders, plain
claude-remote-sync launch(no--folder, noCLAUDE_REMOTE_WORKSPACE, no--yes): confirm the folder picker appears, and picking a subfoldercds into it on the remote with that subfolder's ownCLAUDE.md/.claude/config picked up. Launch into a second, different subfolder without killing the first tmux session; confirmtmux lsshows two distinct sessions, each stillcd'd correctly into its own folder. - [ ]
claude-remote-sync launch --folder <name>: confirm it skips the picker and launches directly in that folder;claude-remote-sync launch --folder <bogus-name>fails immediately with the valid-choices list, before attempting any SSH connection. - [ ] Edit a file in the workspace on the remote; confirm it propagates back to the control machine.
- [ ] Conflict direction (highest-risk item — see the WHY-comment above
createSessioninsrc/sync.ts): pause or disconnect the workspace Mutagen session (mutagen sync pause <name>), edit the same file on both the control machine and the remote with different content, then resume/reconnect (mutagen sync resume <name>) and let it resolve. Confirm the remote's edit is the one that survives. If the control machine's edit survives instead, that confirms the Critical finding's risk materialized —--default-conflict-resolution=betaeither isn't a real flag or doesn't overridetwo-way-resolved's default, and the conflict-resolution flag needs fixing before this tool's "remote wins" claim can be trusted. - [ ]
claude-remote-sync status: confirm both sessions show as syncing and SSH shows as reachable. - [ ]
claude-remote-sync monitor: confirm both the Sync section and both Performance sections render without errors against a real remote, and the numbers roughly match what the control machine's own system monitor (Activity Monitor on macOS, Task Manager on Windows,htopon Linux) andhtopon the remote report at the same moment. - [ ] While
monitoris running, briefly disconnect the remote (e.g. disable Wi-Fi for a few seconds): confirm the remote Performance section shows the(stale — ...)marker instead of crashing the dashboard, and recovers automatically once connectivity returns. - [ ] Ctrl+C out of
monitor, then check for leftover processes/sockets:ps aux | grep '[s]sh -f -N -M'should show nothing, and the control socket file (/tmp/claude-remote-ssh-*.sock) should be gone.
