Launch Claude Code on your Mac while transparently executing every Bash command on a remote Linux host via SSH. Anthropic credentials never touch the remote host.
The default Claude Code workflow for "operate on a remote box" is to install Claude Code on that box and SSH into it. That puts your OAuth token on a machine you may not fully control. remote-launcher keeps Claude (and your token) on the Mac, and ships only individual shell commands across SSH. Files created during a session live on the remote host where they're actually needed.
┌────────────────────────┐ ┌─────────────────────────┐
│ Mac │ │ Remote VM │
│ │ │ │
│ ┌────────────────────┐ │ │ │
│ │ Claude Code │ │ │ │
│ │ ─ Read/Edit/Write ─┼─┼── Mac │ │
│ │ ─ Bash tool ───────┼─┼─SSH────►│ /bin/bash → executes │
│ └────────────────────┘ │ │ here, files here │
│ ▲ │ │ │
│ │ OAuth from keychain│ │ │
│ │ stays here │ │ │
└───┴────────────────────┘ └─────────────────────────┘
- Bash tool → VM via
CLAUDE_CODE_SHELLenv var (officially supported by Claude Code 2.0.65+). - Read/Edit/Write tools → Mac. A system-prompt addendum tells the model to use Bash heredoc for VM-side files.
- Token isolation.
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1strips Anthropic credentials from the bash subprocess environment before SSH runs.
- macOS (Apple Silicon recommended for the test harness; the launcher itself runs anywhere with
bash+ssh) - Claude Code 2.0.65 or later
- SSH key auth to your target host (no passwords, no agent forwarding by default)
- For tests: apple/container (macOS 26+) and
jq
git clone https://github.com/foxfollow/remote-launcher.git ~/code/remote-launcher
cd ~/code/remote-launcher
./install.shinstall.sh is non-destructive: it creates symlinks in ~/.local/bin/ and registers the skill in ~/.claude/skills/remote-launcher/.
Make sure ~/.local/bin is in your PATH.
remote-launcher myvm # bare interactive
remote-launcher myvm --task ~/projects/demo/TASK.md # pre-load a task
remote-launcher myvm --confirm-bash # require approval for every Bash call
remote-launcher myvm -pfp # auto-accept changed host fingerprint (after VM snapshot/rebuild)
remote-launcher winhost --shell powershell # force PowerShell on the remote (default: auto-detect)
remote-launcher myvm -- --model claude-opus-4-7 # forward args to claudeRemote shell flavor. By default the launcher probes the host and picks
between POSIX (/bin/sh) and PowerShell (pwsh or powershell). When it
detects PowerShell, it loads a PowerShell-flavored system prompt so the
model writes Get-Content / Set-Content / Select-String instead of
cat / grep / sed, and runs commands via -EncodedCommand so quoting
stays sane. Override with --shell posix|powershell if auto-detect picks
wrong.
Bash auto-approve. By default the launcher passes
--allowedTools 'Bash(*)' to Claude so every Bash call runs without an
approval prompt. The Bash tool here means "go through SSH to the VM" —
the VM is physically isolated from the Mac, so the prompts add friction
without protection. Read/Edit/Write keep prompting because they hit
the Mac filesystem. Pass --confirm-bash to restore prompts for Bash
too (useful when the VM holds something you care about).
A walkthrough of the simplest case (single agent installs nginx and
verifies) is in examples/single-agent.md.
Give a single Claude session access to two or three VMs at once and let it coordinate between them. Useful when you have, say, a web VM and a DB VM and want one agent to set up the app on one and the schema on the other.
remote-launcher webvm --host dbvm # default=webvm, also dbvm
remote-launcher webvm --host dbvm --host cachevm # three hostsInside Claude, route a Bash call by prefixing it with @<host>:
@webvm systemctl status nginx
@dbvm psql -c '\dt'
hostname # no prefix → goes to the default host (webvm)
The launcher injects a multi-host block into the system prompt listing the
roster and rules. Each host has its own working directory, ControlMaster
socket, and shell-mode cache — cd /tmp on webvm does not affect dbvm.
Filesystems are independent: to move a file between hosts, the agent uses
scp (host-to-host) or pulls to the Mac and pushes back.
A worked example is in examples/multi-host.md.
--shared-workdir <path>— auto-sync a Mac directory to all hosts so Read/Edit/Write on the Mac propagates to every VM.@copy hostA:/path hostB:/pathhelper for inter-host file transfer.- ProxyJump / bastion examples.
Multiple Claude sessions, each in its own terminal, all targeting the same VM:
# Terminal 1 — init agent (sequential)
remote-launcher myvm --task ~/projects/demo/MASTER.md
# inside Claude: "You are Agent 0."
# Terminals 2 and 3 — workers (parallel)
remote-launcher myvm --task ~/projects/demo/MASTER.md # × 2
# inside each: "You are Agent 1." / 2
# Terminal 4 — reviewer (sequential)
remote-launcher myvm --task ~/projects/demo/MASTER.md
# "You are Agent 3."
# Pull artifacts back
scp -r myvm:~/multi-demo ~/Downloads/A complete worked example with a shared MASTER.md is in
examples/multi-agent.md.
See docs/security-model.md. Short version:
| Concern | How it's handled |
|---|---|
| Anthropic OAuth on VM | macOS keychain only; CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 strips Anthropic env from subprocess env |
| Credentials in env passed via SSH | OpenSSH does not forward env unless SendEnv is set; we don't set it |
| SSH agent / key chaining | Agent forwarding off by default (no -A) |
| Credentials accidentally written to disk on VM | Files originate from bash heredocs on the VM; Mac-side never writes secret files |
| Connection theft after disconnect | ControlPersist=15m; the socket is in $TMPDIR of your Mac user only |
The full security model is verified by the test suite — see tests/.
remote-launcher-doctor # files / PATH check only
remote-launcher-doctor myvm # also live SSH + wrapper round-tripcd tests
./run-tests.shThe harness uses Apple's container to spin up an Ubuntu micro-VM with sshd, generates a fresh ed25519 keypair just for the test, and exercises the launcher against it. See tests/README.md.
- Interactive TUIs (
vim,htop) on the VM may render poorly — the wrapper does not allocate a pty. - Each shell call is a fresh remote shell; environment and aliases do not persist between calls. Working directory does (tracked via state file).
- No file sync. By design — files live on the VM. To pull artifacts:
scp -r myvm:path ~/local.
MIT. See LICENSE.
See SECURITY.md.