A self-hosted app that runs the actual claude CLI on your Claude subscription and puts its real terminal UI in your pocket. Not a chat that reimplements Claude Code — a live terminal bridged straight to the claude TUI running on your machine. What you'd see at your desk, you now see on your phone: the same prompts, the same questions, the same subagents, the same everything.
📱 your phone → 🔒 your machine (Remote Coder) → 🤖 claude CLI (your subscription)
Self-hosted · no API key · your code never leaves your machine · secured by a token · MIT
Try it in ~60 seconds — on the machine that has claude installed + logged in:
curl -fsSL https://raw.githubusercontent.com/burakgon/remote-coder/main/scripts/install.sh | bashClones, builds, and starts the server — then prints a one-time connect link to open on your phone. Prefer to read it first? See Quickstart.
You run a small server on your dev machine. It launches the real Claude Code CLI as a subprocess — on your own subscription, no API key — inside a persistent terminal, and serves a polished, installable app you open from your phone or any browser. The app is a true terminal (xterm.js) wired straight to that claude session, so you're not looking at a reinterpretation of Claude Code — you're looking at Claude Code itself, live, from anywhere.
That framing is the whole point:
- Nothing is reimplemented, so nothing is lost. Permission prompts, multiple-choice questions, subagent panels, slash commands, thinking, diffs — they all just work, because it's the genuine TUI, not a bespoke chat trying to keep up with it.
- It survives real life. The session lives in
tmuxon your machine. Lock your phone, lose signal, close the app, switch networks — reconnect and it re-attaches exactly where it was, command still running. - It's actually usable by thumb. A full-screen terminal on a touchscreen is normally miserable; the hard part Remote Coder solves is the ergonomics — a Termux-style key bar, sticky Ctrl, two-finger scroll to read back, and tap-to-select copy.
It's host-native (your machine, your files, your ~/.claude), secure by default (a mandatory access token), and MIT licensed.
Anthropic ships first-party remote control and chat bots — but claude remote-control can only resume a session that was already started at the machine, and the third-party chat bots reinterpret Claude Code into a messaging UI, so they drift, drop features, and can't answer its prompts. The moment Claude needs a decision, you're stuck until you're back at your desk.
Remote Coder closes that gap by refusing to reinterpret anything — it just gives you the real terminal:
claude remote-control |
Telegram / Discord bots | Remote Coder | |
|---|---|---|---|
| Start a brand-new session remotely | resume only | ✗ | ✓ |
| The real Claude Code TUI, nothing reinterpreted | resume only | ✗ | ✓ |
| Approve/deny tool use · answer questions, as at your desk | — | ✗ | ✓ |
| Survives a dropped connection / closed app (tmux) | ✗ | ✗ | ✓ |
| Files to and from the agent | ✗ | Telegram only | ✓ |
| Run several sessions at once | — | ✗ | ✓ |
| Split screen — sessions side by side (iTerm2-style) | — | ✗ | ✓ |
| Live status per session — see which one needs you | — | ✗ | ✓ |
| Installable app · self-hosted · MIT | — | — | ✓ |
The app renders the actual claude fullscreen TUI in a real terminal — colors, box-drawing, the logo, the lot. When Claude asks to run a tool, you get its own permission prompt; when it asks a multiple-choice question, you get its own picker; when it dispatches subagents, you watch them exactly as you would under the textbox at your desk. There's no feature to fall behind on, because it is Claude Code.
On a desktop browser the workspace splits iTerm2-style: open panes from the header or by dragging a session from the rail onto a pane's edge, drag a pane by its title bar to rearrange (or flip a side-by-side split into a stacked one), resize with the dividers, and the layout persists across reloads. Closing a pane never kills the session — it keeps running in tmux, right there in the rail.
A TUI on a phone is only good if you can actually drive it. Remote Coder adds a Termux-style key bar (Esc, Tab, arrows, Home/End, PgUp/PgDn, / - | ~, ^C, ^D, Paste) with a sticky Ctrl that turns your next keystroke into a control chord. Two fingers scroll back through the transcript, a pinned Select button opens a plain, selectable copy of the screen for the OS copy menu, and --dangerously-skip-permissions is a clearly-marked, per-session toggle when you want it.
Every session is a tmux session on your machine, and the terminal WebSocket re-attaches on reconnect. A locked phone, a subway tunnel, a killed app, a Wi-Fi→cellular hop — none of it interrupts the work. Come back and Claude is still there, still running, right where you left it.
Upload images and files into a session, browse and download host files, and just ask Claude to send you a file or image — it lands in the session's Files panel to view full-size or download. Screenshots in, a generated chart out, all from the phone.
A live sessions rail (a bottom sheet on mobile, a permanent pane on desktop) lists every running claude with a live status per session: working while Claude is generating — including when its main loop is quiet but background agents are still going — a loud coral needs you the moment it actually blocks on a question or permission, and a calm idle when a turn is done. The status is read from the session's real terminal on the server, so it's accurate even for sessions you never have on screen. The rail also shows your subscription usage (the 5-hour and weekly limits), and starts a new session anywhere via a git-aware directory picker.
An installable PWA (Add to Home Screen, no app store) and Web Push when a session finishes or needs a decision — so you can walk away and get pulled back only when it matters.
An OLED true-black theme (Settings → Appearance — #000 pixels are literally off on an OLED panel, so it saves battery and blacks read bottomless), saved defaults for new sessions (model, thinking effort, permission mode, even the clearly-marked --dangerously-skip-permissions toggle) that every new-session screen starts from, and per-session renames so the rail reads the way you think.
When a new version lands on GitHub, the app shows an update notice with the version and a grouped changelog. Tap Update now and the server pulls, rebuilds, and restarts itself, then reconnects on the new version — no SSH, no git pull. A failed build leaves the running server untouched.
Fastest path — one command (clones into ~/remote-coder, builds, starts, prints the connect link):
curl -fsSL https://raw.githubusercontent.com/burakgon/remote-coder/main/scripts/install.sh | bashIt preflights Node/pnpm/claude/tmux and tells you exactly what's missing. Prefer to do it by hand? Read on.
You need:
- Node ≥ 24. Check with
node --version. - pnpm. The easiest way is
corepack enable(ships with Node) — thenpnpmjust works in the repo. Otherwisenpm i -g pnpm. - tmux. Each session runs inside tmux so it survives disconnects.
brew install tmux(macOS) /apt install tmux(Debian/Ubuntu). Run it with a UTF-8 locale so Claude's box-drawing glyphs render. - Claude Code installed and logged in on this machine. Run
claudeonce in a terminal here and complete the login — there is no remote login, and a missing/unauthenticatedclaudeis the #1 first-run failure (the app tells you which it is, and/diagshowsclaude.available). - A working native build of
better-sqlite3.pnpm installbuilds it; if your toolchain can't, the server still boots but falls back to a non-durable in-memory store (sessions vanish on every restart). It logs a loud warning and/diagreportsstoreMode: "memory-fallback"— see Troubleshooting.
git clone https://github.com/burakgon/remote-coder && cd remote-coder
corepack enable # makes `pnpm` available (or: npm i -g pnpm)
pnpm install && pnpm build
node packages/cli/dist/index.jsIt generates an access token and prints a ready-to-use link:
Remote Coder is running.
Access token generated and stored in the data dir. Open this link to connect:
http://127.0.0.1:4280/?token=<token>
Open it on the same machine — then read From your phone to reach it remotely.
npx remote-coderisn't published yet — the CLI isprivatewhile the monorepo stabilizes. Clone + build is the supported path today.
The server binds to 127.0.0.1 and should not be exposed directly. Put an HTTPS tunnel in front of it (the installable app and Web Push both require HTTPS) — your machine stays the host, and the token is still enforced on every request through the tunnel.
# with the server running on 127.0.0.1:4280
cloudflared tunnel --url http://127.0.0.1:4280Open the printed https://… link on your phone, paste the token (or use the ?token=… link), Add to Home Screen, and turn on notifications. (Tailscale Serve works too: tailscale serve --bg http://127.0.0.1:4280.)
⚠️ cloudflared tunnel --urlgives you an ephemeraltrycloudflare.comURL that changes every run. That's fine for a quick try, but an installed PWA is bound to the origin you installed it from — when the URL changes, your home-screen app points at a dead origin and push deep-links break. For real day-to-day use, set up a named/stable tunnel (a fixed hostname) — Cloudflare Named Tunnel, or Tailscale Serve, whose…ts.nethostname is stable — and setREMOTE_CODER_PUBLIC_URLto that origin so push notifications click through to the right place.
Run it as a background service · flags · environment variables
node packages/cli/dist/index.js install writes a per-user service unit (macOS LaunchAgent / Linux systemd --user) and prints the one command to enable it — nothing auto-starts until you opt in. It runs as you, not root. On macOS it runs while you're logged in (Claude's subscription auth needs a real login session).
| Var | Default | Purpose |
|---|---|---|
PORT |
4280 |
Listen port (0 = OS-chosen). |
BIND_ADDRESS |
127.0.0.1 |
Keep loopback; use a tunnel for remote. |
ACCESS_TOKEN |
(generated) | Override the token (used verbatim, never written to disk). |
NO_TOKEN |
(unset) | 1 = tokenless dev mode. Loopback binds only — it refuses to start non-loopback. |
FS_ROOT |
$HOME (then cwd) |
Confine the file picker / fs endpoints to a subtree. Does not sandbox the agent (see Security). |
MAX_UPLOAD_BYTES |
26214400 |
Upload size cap (25 MiB). |
REMOTE_CODER_DATA_DIR |
~/.config/remote-coder¹ |
SQLite DB, token, VAPID keys, logs (mode 0700). |
REMOTE_CODER_PUBLIC_URL |
(bind URL) | Your user-facing origin (the tunnel URL). Set this behind a tunnel: it's the click-target for push notifications and an allowed Origin. |
TRUST_PROXY |
false |
1/true = honor X-Forwarded-For behind a reverse proxy, so the per-client lockout/rate-limit key on the real client IP (not the proxy's). |
REMOTE_CODER_ALLOWED_ORIGINS |
(empty) | Comma-separated extra Origins the CSWSH guard allows (beyond same-origin/loopback/PUBLIC_URL). |
REMOTE_CODER_RATE_LIMIT_RPM |
600 |
Sustained requests/minute per client. 0 disables the limiter. |
REMOTE_CODER_RATE_LIMIT_BURST |
120 |
Instantaneous burst allowance (token-bucket). |
REMOTE_CODER_MAX_SESSIONS |
25 |
Max concurrent live claude sessions; new spawns get 429 at the cap. 0 = unbounded. |
CLAUDE_BIN |
claude |
Path/name of the Claude Code CLI to spawn (must be on the service's PATH). |
VAPID_SUBJECT |
mailto:remote-coder@localhost |
mailto:/URL contact in the Web Push VAPID claim. |
WEB_DIR |
(bundled) | Override the path to the built PWA (packages/web/dist). |
XDG_CONFIG_HOME |
(unset) | When REMOTE_CODER_DATA_DIR is unset, the data dir is $XDG_CONFIG_HOME/remote-coder. |
REMOTE_CODER_SERVICE_MANAGER / _LABEL |
(auto) | Override which service the OTA self-updater restarts (launchd/systemd + label). Normally read from service.json. |
¹ REMOTE_CODER_DATA_DIR → else $XDG_CONFIG_HOME/remote-coder → else ~/.config/remote-coder → else ./.remote-coder.
The access token never enters argv (it lives in a 0600 file). ANTHROPIC_API_KEY is always stripped from the spawned claude (subscription auth only). The token-rotation grace window (old token honored briefly after POST /token/rotate) is a fixed 60s and is not env-tunable. --port <n>, --bind <addr>, --no-token (loopback dev only) are also available; --help for the full list.
- macOS (LaunchAgent): stdout →
<data-dir>/remote-coder.log, stderr →<data-dir>/remote-coder.err.log(<data-dir>defaults to~/.config/remote-coder). These are not rotated — cap them with the OS log rotator (anewsyslog.dentry) or periodically truncate.tail -f ~/.config/remote-coder/remote-coder.err.log. - Linux (
systemd --user): logs go to journald —journalctl --user -u remote-coder -f(journald already size-bounds itself; tune withjournalctl --user --vacuum-size=50M). GET /diag(token-gated, like every API route) returns a JSON health snapshot: running build sha + whether it drifted from the checkout, store mode (sqlitevs the non-durablememory-fallback),claudeavailability + version, Node version, and the last update state. Openhttps://<host>/diagwith the token header, orcurl -H "Authorization: Bearer <token>" http://127.0.0.1:4280/diag.GET /healthis the only unauthenticated route (returns{ ok: true }only).
Remote Coder is, by design, remote code execution on your own machine — that's the whole point. Treat the token like an SSH key.
- Single mandatory token on every request and WebSocket — constant-time check, per-client lockout. It is a single shared secret (not per-user/per-device): anyone with it has full access. It refuses to start on a non-loopback bind without one. Rotate it anytime with
POST /token/rotate(the old token is honored for a 60s grace, then rejected; the app re-stores the new one). - HTTPS for anything remote — a plain public port leaks the token. Always tunnel.
- The permission gate stays on — you approve every tool from the terminal, exactly as you would at your desk.
--dangerously-skip-permissionsis per-session, off by default, and clearly marked. ⚠️ The agent is NOT sandboxed. Theclaudesubprocess runs as you, with your full machine access — it can run any command and touch any file your user can.FS_ROOTonly scopes Remote Coder's own file-browser/upload/download endpoints; it does not confine whatclaudeitself can read or write. Run this only on a machine you'd hand someone with your shell.- Defense-in-depth controls (all on by default, tunable — see the env table): a cross-origin (CSWSH) guard rejects a present, cross-origin, non-allow-listed
Origin(REMOTE_CODER_ALLOWED_ORIGINS,REMOTE_CODER_PUBLIC_URL); a per-client rate limiter (REMOTE_CODER_RATE_LIMIT_RPM/_BURST,0disables); a concurrency cap on live sessions (REMOTE_CODER_MAX_SESSIONS); andTRUST_PROXYso those keys on the real client IP behind a proxy.
Stuck or unsure? See docs/troubleshooting.md for the common first-run and runtime failures.
- 💬 Questions, ideas, "show your setup" → GitHub Discussions
- 🐛 Bugs / feature requests → Issues
- 🔒 Security → SECURITY.md
- 🤝 Contributing → CONTRIBUTING.md
If it's useful to you, a ⭐ genuinely helps other Claude Code users find it.
Full-TypeScript pnpm monorepo — server · web · cli. The server bridges a terminal WebSocket to the claude TUI running under tmux (via node-pty); the web app is an installable React PWA built on xterm.js.
pnpm install && pnpm build
pnpm typecheck && pnpm lint && pnpm testReleased under the MIT license.






