Skip to content

Hosting a Jam

Roy Padina edited this page Aug 29, 2026 · 7 revisions

Hosting a Jam

You keep the real claude TUI — your plugins, skills, MCP servers, CLAUDE.md, hooks and account. You also join your own jam from a client, exactly like everybody else.

Start one

claude-jam host --name Roy --cwd ~/Code/some-project -- --model sonnet

Or run claude-jam with no arguments and use the launcher menu — its Host screen collects the same fields and prints the exact command line before it runs it.

That builds a detached tmux session (two windows: daemon, and claude — the real TUI) on a tmux server of claude-jam's own, socket claude-jam-<port>, and opens your own client in the terminal you launched from. Because it is claude-jam's own server it can bind a bare F3 to detach-client without touching your tmux config, and it cannot see your own sessions at all.

Or adopt the session you are already in

claude-jam host --resume <id> restarts a conversation in a pane of claude-jam's own. claude-jam adopt does not restart anything — it shares the claude that is already running, in the tmux pane it is already in.

# from inside that session — claude can run this for you from the Bash tool
claude-jam adopt

# from another terminal, naming the pane (and its tmux server if it is not yours)
claude-jam adopt --pane %23 --socket default --token ourshared1

It takes any claude-jam host flag as well, so claude-jam adopt --tunnel --jam-name friday works. It shows what it resolved before it shares anything — the pane, the tmux server, what is running in it, the directory, the session id it worked out, and that session's first message and last answer — then asks. --yes skips the question for scripting, and refuses outright when the transcript it picked looks stale: the failure this guards against is sharing the wrong conversation with the room.

Not inside tmux? A bare terminal, an IDE terminal or a cmux pane has no pane to capture or type into, so adoption is impossible. claude-jam says so and hands you the whole alternative with the id already filled in: claude-jam host --resume <session-id> --cwd <dir>.

claude-jam did not create this session, so it will never end it. Adoption is the first time claude-jam points tmux at a server it does not own, and the rule there is narrow and total: it only READS (capture-pane, display-message, list-panes) and TYPES INTO THE ADOPTED PANE. No new-session, no kill-session, no set-option — not the ownership marker, not the status line, not the fill character — and no key binding, because a tmux key table is server-global and that server is yours. The daemon runs in a tmux session of claude-jam's own, so claude-jam end has something of its own to end: it stops the daemon and its children and leaves the pane, its session and claude exactly as they were. claude-jam sessions marks the row adopted; claude-jam clean never touches it.

At adoption claude is told it is now shared: one injected message, prefixed [claude-jam:tool]: so it is visibly from the tool and not from a participant, carrying the protocol, the two standing rules, the digest, who is here and where MANUAL.md is. It is re-sent after a /compact or /clear — that is exactly when injected context disappears — and on a meaningful roster change while the session is idle, at most one every ten minutes. --no-brief skips the first one; --brief-updates off stops the later ones.

Two ceilings are inherent to adopting rather than starting, and are not worked around:

  • No Stop/Notification hooks. A running claude cannot be given new ones (--settings is read once, at startup), so turn-end and permission-wait come from the pane classifier — the screen, read 2.5 times a second — which is the authoritative source anyway.
  • The pane is not resized. It is your window, usually with you looking at it, so a guest on a much smaller terminal sees it letterboxed rather than reflowed.

/jam — optional

integrations/claude-plugin/ in the repository is a three-file Claude Code plugin (a command, a skill, a manifest — no code) mapping /jam, /jam invite <Name>, /jam end and /jam status onto the CLI:

/plugin marketplace add roypadina/claude-jam
/plugin install claude-jam@claude-jam

Entirely optional. claude-jam adopt from the Bash tool works without it. /jam is just shorter, and the skill in the plugin carries the two standing rules to a claude that has not been briefed yet.

Note what /jam with no argument does: claude-jam adopt run from the Bash tool has no terminal to ask a yes/no question on, so it prints everything it resolved and stops. The command tells claude to show you that block and wait for your confirmation before re-running with --yes.

Flags

flag default what it does
--name <X> Host how you appear to everyone, and to claude
--cwd <dir> the current directory where claude runs
--port <N> 7777 the WebSocket port. The state dir and tmux socket follow it
--tmux <name> claude-jam the tmux session name — the identifier claude-jam end takes. A second jam auto-names claude-jam-2
--jam-name <X> this directory's name what the jam is called: the welcome line, claude-jam sessions, /menu, and what people on your network see. Cosmetic — never used for auth or for a path
--no-announce off (it announces) do not announce this jam on the local network. See Security Model for what announcing discloses
--token <value> none (knock-only) 8–64 chars of [A-Za-z0-9_-]
--invite-only off refuse knocks outright: an invite link is the only door
--view off also serve the TUI read-only in a browser (needs ttyd)
--view-port <N> port + 1 where that browser view listens
--tunnel off a Cloudflare quick tunnel (needs cloudflared)
--funnel off Tailscale Funnel instead (needs tailscale) — mutually exclusive with --tunnel
--replay <N> a default backlog how many transcript events a joining guest is shown
--answers host|anyone anyone who may /answer a question. Permissions are always yours
--uploads ask|auto|off ask whether you are asked about every file a guest sends. See Files and Export — the caps never move
--upload-quota <n>[MB|files] 40 files / 200 MB how much an auto session may take before it falls back to asking
--export ask|auto|off ask the transcript's own toggle, deliberately separate — it does not follow --uploads
--no-sound off (it sounds) start your client silent. The bell and the notification are separate toggles in /menu → Notifications
--config-dir <D> your default run claude under another profile (CLAUDE_CONFIG_DIR)
--resume <session-id> none continue an existing claude session — it is restarted in a pane of claude-jam's own. To share one without restarting it, use claude-jam adopt
--no-system-prompt off keep the shared-session contract in the hooks only
--no-token-in-context off never put the token in claude's context
--no-popup off no tmux display-popup approval prompts
--tmux-socket <name> claude-jam-<port> default puts it on your shared tmux server (F3-out is then not bound)
--attach reopen your client on a jam that is already running
--no-attach off do not open a client at all
--no-prompt / --keep-on-exit / --end-on-exit prompt answer the on-exit question up front
-- <claude args> everything after -- goes to claude verbatim

claude-jam host --help prints the same list.

Letting people in

Four doors, all switchable while the jam runs from /menu → Access. See Security Model for what each one is actually worth.

mode how who decides
invite link claude-jam invite Dana mints cjam1_…; claude-jam join <link> is the guest's whole command you, per person, in advance — and revocably
token --token <value>, or /token set later. One shared secret whoever holds the string
knock no token. The guest waits; you get ⚑ Dana wants to join (100.86.8.97) and answer /accept Dana you, per person, live
invite-only --invite-only, or /token invite-only on. Knocks are refused with a reason you, per link

Invite links

claude-jam invite Dana                          # multi-use, 24 h
claude-jam invite Dana --uses 1 --expires 30m   # one shot, half an hour
claude-jam invites                              # id, name, state, uses, expiry — never the link again
claude-jam invite revoke Dana                   # or revoke <id>

The same three from inside the client: /invite Dana, /invites, /invite revoke Dana.

Defaults are multi-use and 24 hours, because a guest whose laptop slept has to be able to come back. The daemon stores only a hash of each secret, in the 0700 state dir, and reloads them on restart — so a daemon that came back does not lock out the people it already invited. Anything wrong with a link (tampered, expired, revoked, used up, name already connected) is said out loud and then falls through to a knock, so nobody is silently rejected.

The approval bar

Every request — a knock, a guest's claude command, a file, an export, a permission answer — raises one row just above your status row:

⚑ Dana wants to join (100.86.8.97) · [a]ccept [d]eny [i]gnore · 2:00

counting down to that request's own expiry, with +N more when several wait. One key answers it, and only while your input line is empty — the first character you type disarms the keys until Esc, so a message starting with d can never deny anybody. Anyone attached to the tmux session also gets a one-key tmux display-popup; whoever answers first wins.

Running it

Your client is the same client a guest gets, plus the host-only commands. The two keys that are yours alone:

  • F3 — attach the real TUI. tmux attach takes the terminal, so permission prompts, pickers, the mouse and Ctrl-C all work at native speed. F3 again (or Ctrl-b d) comes back. Host and loopback only.
  • a d i/Esc — the approval bar, on an empty input line.

Host-only commands: /join, /token new|set|off, /token invite-only on|off, /remote off|tunnel|funnel, /invite, /invites, /invite revoke, /kick <name> [revoke], /end, /accept, /deny, /allow-cmd, /deny-cmd, /allow-perm, /deny-perm, /allow-export, /deny-export, /accept-file, /deny-file, and /answer other <text>.

/menu is the live control panel: People · Invites · Access · Session · Notifications · Help & guides, with the jam's own state shown next to every toggle.

Hearing what needs you

You are the only person who hears arrivals — a guest has nobody to approve — and the two sounds are different on purpose, so you know without looking whether somebody is waiting:

moment sound repeats
a knock — they are waiting for you Submarine, a slow low tone, plus ⚑ <Name> wants to join once after 30 s if nobody answers, then never
a token or invite-link join — they are already in Glass, one short chime, <Name> joined no
somebody nudges you (/ping, below) Hero only with /ping <Name> !
somebody leaves (silent — the roster line is enough)

Three independent toggles in /menu → Notifications: sound, desktop notification, terminal bell. --no-sound starts silent and /sound on|off flips it from the keyboard — and it silences the sound only, so the line and the notification still arrive. On Linux the sounds go through paplay or aplay if either is installed and are silently skipped if not.

Getting somebody's attention

/ping <Name> [message] (alias /nudge) is available to everyone, you included — see Joining a Jam. /who tells you who is active, idle 4m or away 20m+ before you spend one.

A second jam

claude-jam host --tmux work-jam --port 7779 --cwd ~/Code/other

Each jam gets its own port, its own state dir ($TMPDIR/claude-jam-<port>) and its own tmux socket. claude-jam host on a name already held by one of claude-jam's own offers [a]ttach as host · [n]ew session (claude-jam-2) · [e]nd it and start fresh · [c]ancel.

Ending it

claude-jam sessions          # what is running: name, port, state, uptime, who is here, cwd
claude-jam sessions --json   # the same, for a script
claude-jam end [name]        # end one. No name and one jam → that one; several → a numbered picker
claude-jam end --all         # after an explicit confirmation, re-verifying every one
claude-jam clean             # remove state dirs whose session is gone, and only those
claude-jam host --attach     # reopen your client on a jam that is still running

/end inside the client does the same, after really end this jam for everyone? [y/N].

Closing your client does not end the jam: you are asked this jam is still running (2 guests connected) — [k]eep it running · [e]nd it · [c]ancel. A stdin that is not a terminal counts as keep, so nothing destructive happens because nobody was there to answer.

An adopted jam ends differently, and that is the point. claude-jam end on one stops the daemon and its children and leaves the pane, its tmux session and claude exactly as they were — claude-jam did not start that session, so it never ends it. The row says adopted, and claude-jam clean leaves it alone.

claude-jam only ever ends a tmux session it created. On creation it stamps @claude-jam-owned <state-dir> on the session and writes session.json into that directory; ending anything requires that pair to line up for the exact name you gave. There is no name pattern, no filtered sweep over tmux list-sessions, no kill-server, and --all re-verifies every session it touches. A session started by claude-jam 0.18.0 carries the older @jam-owned marker and is still recognised — that name is read as well as the new one.

Letting people find it

By default your jam announces itself on the local network, so a guest on the same wifi runs claude-jam find (or just claude-jam join) and picks it out of a list instead of waiting for you to send an address. What is announced is the jam's name, your display name, eight characters of the session id, which kind of door it is, whether a browser view exists, and the version — never the token, an invite secret or any path.

It is still a disclosure: everyone on that network learns the jam exists, what it is called and who is hosting it. On your own network that is the point; on café wifi it is not. --no-announce at launch, or /menu → Access → Announce on the network while it runs, turns it off — and remember the default name is this directory's name, so --jam-name "friday" is worth typing before a client name goes on somebody else's wifi.

Finding a jam is never entering one: whichever door you set up is still the door. Full detail in Security Model.

Next

Joining a Jam · Remote Access · Files and Export · Security Model · Troubleshooting

Clone this wiki locally