-
Notifications
You must be signed in to change notification settings - Fork 0
Remote Access
The daemon binds 0.0.0.0 and the only authentication is the token (or an invite link) plus the
host's own /accept. Never put that on a public IP you advertise. Everything below is a way
of moving the bytes; none of it adds a second factor.
| route | how | what it costs |
|---|---|---|
| LAN | the printed ws://<host-ip>:7777 line. Works with no extra software |
everyone has to be on the same network |
| SSH tunnel |
ssh -L 7777:127.0.0.1:7777 host, then claude-jam join ws://127.0.0.1:7777 …
|
the guest needs an SSH account on the host |
| Tailscale | install Tailscale on both machines. If it is installed, the printed join line already uses the tailnet IP | both sides join the tailnet |
| a public relay |
--tunnel or --funnel, below |
the URL is the only thing between the internet and your session — keep it private |
Needs cloudflared on PATH.
claude-jam host --name Roy --cwd . --tunnelSpawns two quick tunnels (one for the WebSocket, one for the browser view if --view is on) and
prints wss://<random-words>.trycloudflare.com join and view URLs. Your guest installs nothing.
The hostname is random and changes on every respawn. That is the one real gotcha: an invite
link fixes its addresses at the moment it is minted, so links minted before a respawn reach the
jam only over their LAN address. Mint fresh ones, or use --funnel.
Needs tailscale on PATH and Funnel enabled for the tailnet.
claude-jam host --name Roy --cwd . --funnelSame job, but the URL is your node's own name — wss://<machine>.<tailnet>.ts.net, and
https://…:8443 for the view — so it is the same every run. Your guest still installs
nothing and does not need to be on your tailnet.
Two things need a human, once:
-
Grant the
funnelnode attribute for the tailnet in Tailscale's Access Controls. Without it the CLI refuses and claude-jam repeats the reason verbatim. -
Use the standalone Tailscale build on macOS. The App Store build is sandboxed and cannot
change funnel configuration at all — its CLI answers
The Tailscale GUI failed to start … (Tailscale.CLIError error 3.). The build from tailscale.com can.--funnel-cli <path>points at a CLI elsewhere.
--tunnel is always available as the fallback.
Known risk, upstream. Tailscale issue
#18827 — open since 2026-02-27 — reports
WebSockets through tailscale serve's HTTP reverse proxy, the layer Funnel rides, closing every
10–40 s with code 1001 "Going Away". claude-jam pings every 30 s, which clears Cloudflare's 100 s
idle cap but cannot outrun a 10 s drop, and no jam has yet been run over Funnel end to end. So
take --funnel for the property it is proven to have — a URL that never changes — and prefer
--tunnel for a long sitting until somebody proves otherwise.
Neither relay is launch-only. Three surfaces, one code path:
claude-jam remote off|tunnel|funnel [--jam NAME] [--reissue] # from a shell-
/remote off|tunnel|funnelinside the host's client /menu → Access → Remote
Nobody already connected is dropped. A mode that cannot run here says why, with the exact
fix. When a relay comes up, host clients get tunnel ready: <the whole join command> · give it a few seconds — the edge needs a moment before the first join works rather than a silent refresh.
Those few seconds are measured, not a hedge. cloudflared reports the hostname as soon as it has one; the Cloudflare edge needs another ~2.5 s before it will route to it. Timed on a 2 h 10 m tunnel soak, 2026-08-30:
21:10:51.616 tunnel up: sally-consideration-visitor-autos.trycloudflare.com
21:10:51.922 guest socket error, then closed code=1006 (306 ms later)
21:10:54.142 guest connected (2.53 s after the URL was published)
A person pasting a link into a chat is slower than that, and a client reconnects on its own, so
in practice it costs nothing — it was the entire explanation for that soak's single reconnect.
It matters if you are scripting a join: sleep a few seconds after the line appears, or expect
one 1006 and a retry.
Links minted earlier carry the old address, so the switch offers --reissue to mint every live
link again — and it waits for the new hostname first, because minting immediately would
hand out exactly the address it is replacing. claude-jam invites shows the result.
Only one relay at a time: two for one port is a startup error, and the runtime switch keeps that rule.
claude-jam host … --viewNeeds ttyd. Serves the live TUI read-only at http://jam:<token>@<host>:7778 (port + 1 by
default, --view-port moves it). Each tab gets its own grouped tmux session, born
read-only,ignore-size, so viewers never move each other's screen — and never type into yours or
resize it either. Over --tunnel or --funnel the view gets its own URL.
Two things about that URL, both of which matter more than the feature does:
-
It is a credential, and when a token is set it IS the join token. A leaked view URL is a
leaked join link.
/token newrotates both and kills the old view URL;/kickdoes not — it closes a socket and changes no key, so someone you kick keeps the view until you rotate it or turn the view off (/menu → Access → Browser view). -
Read-only is real as of 0.23.1, and was not before. Earlier versions rested the claim on
ttyd's own default, which flipped in ttyd 1.7.0 — so on an olderttyd(or any binary--view-ttydwas pointed at) the view was writable while the tool printed "read-only". Andttydhonours a resize even when it refuses input, so a viewer could shrink the host's live claude pane to an unusable size from anywhere, including through a relay. Both are now enforced on the tmux client instead, which holds whateverttydyou have. See Security Model.
-
A quick tunnel's hostname changes when
cloudflaredrespawns. Guests already connected keep their socket; guests reconnecting seestill retrying — the join URL changed. The host runs/joinand sends the new line, or switches to--funnel. There is a respawn backoff so a flapping relay does not hammer anything. -
Sockets are heartbeated every 30 s. A missed pong shows in the status row as
⚠ stale Ns; the client reconnects on its own, in tiers, so a laptop that slept comes back without anybody typing anything. - A link's addresses are fixed at mint time. A link carries the relay address first and the LAN address second, and the client tries them in order with a 3-second timeout each — so a link whose tunnel is gone still works on the LAN.
-
Revoking or expiring a link disconnects nobody already in. It only stops the next join.
/kick <name> revokeis the pair that actually removes somebody and takes their link back.