Skip to content

Troubleshooting

Roy Padina edited this page Aug 30, 2026 · 3 revisions

Troubleshooting

The failures people actually hit, grouped by where they bite.

Getting in

"still retrying — the join URL changed" — it probably did. The host's --tunnel relay restarted and Cloudflare handed out a fresh random hostname. The host runs /join and sends the new line, or switches to --funnel, whose hostname never changes. Links minted before the respawn still work over their LAN address.

A knock never gets answered — knocks expire after 2 minutes, and at most 10 wait at once. The host has to be looking at their client. Ask them for an invite link instead.

"this jam is invite-only" — knocks are refused on purpose. Ask the host for a claude-jam invite <YourName> link.

A link is refused — the reason is printed: tampered, expired, revoked, used up, or the name is already connected. It then falls through to a knock, so the host can still wave you in. A cjam2_… link means "update claude-jam", not a broken link.

Two people with the same name — refused. Attribution is by name, so two live participants can never share one.

The screen

F2, Shift+Enter, PgUp or the live view do nothing — you are running --basic, which is transcript-only. Drop the flag. (It is also chosen automatically when stdin is not a tty.) /history works in --basic; scrolling the live TUI does not.

"I can only see very little of this conversation" — three separate limits, and the fix is different for each:

what you are hitting which limit what to do
the backlog you were given on arrival stops the host's --replay (300 events by default) /history [n|all] re-prints further back
/history runs out the host's --history — what the jam still keeps (2000 events, 20000 at most) /export is the complete record
PgUp in the live TUI stops the host pane's own scrollback, 2000 lines at most nothing: those lines are gone from the pane too

The client says which one you just hit, once, the first time you scroll to the very top: — that is as far back as this jam kept (N events · host pane 2000 lines) · /export for the full transcript. Nothing is ever truncated silently.

PgUp scrolls my terminal instead of the mirror — your terminal claimed the key. Most terminals send plain PgUp to the program and keep Shift+PgUp for themselves; if yours does the opposite, use Shift+↑/↓ (one line at a time), and /history for the transcript.

The mouse wheel does nothing in the live TUI — expected, unless your terminal is already sending wheel events. claude-jam deliberately never turns mouse reporting on: doing so would take text selection and copy/paste away from you, which is a worse trade than a missing wheel. The keys are the supported way.

A scrolled-back page looks a second or two out of date — it is. A page is cached for 2 s per range, so one PgUp costs the host one capture-pane and a held-down one still costs one. End, then PgUp again, re-reads it.

Flipping F2 used to eat my scrollback — has that stopped? — yes, since v0.28. The live TUI renders in the terminal's alternate screen buffer, so the transcript keeps the normal one untouched. Coming back from an F3 attach no longer re-feeds anything either; before v0.28 it re-fed the last 40 lines and dropped the rest.

My terminal is left in a strange state after the client died — it should not be: the client puts the alternate screen back on exit, on Ctrl-C, on a signal and on an uncaught error, and an error is printed after it has done so. If something ever does leave you stranded, reset (or tput rmcup) puts it right.

The screen looks cropped or half empty — your terminal is smaller than the host's window; the dim — mirror: line says how much was cut. The host's own client keeps the window sized to their terminal, so a guest with a bigger terminal simply sees blank space.

You can see things from before you joined — yes: the daemon replays up to the host's --replay events of the transcript on connect, with a history above · live from here divider under it, and /history reaches whatever else the jam kept. The host can launch with --replay 0 (and --history 0 to keep nothing at all).

Colours moved since an earlier jam — v0.17 swapped one palette slot (a pale green that was too close to the green every client uses for itself). Colours are stable per name; that one slot is rose now.

[masked] where a value should be — the secret deny-list recognised a shape. The value is unchanged on disk and in claude's context; only the copy being shown to other people is masked. There is no per-line override. See Security Model.

F3 and the raw TUI

F3 does nothing — either you are a guest (F3 is host and loopback only), or your terminal sends a different F3 sequence. A guest who needs to answer a prompt wants /answer. A host can tmux attach instead — that is exactly what F3 does for them.

Stuck inside the TUI after F3F3 again detaches, and Ctrl-b d still does the same. A host running --tmux-socket default has only Ctrl-b d, because a bare F3 binding on the shared server would be their whole tmux.

"tmux attach -t claude-jam says there is no session" — claude-jam runs its own tmux server. The line is tmux -L claude-jam-<port> attach -t <name>:claude, and claude-jam sessions prints the exact one for every live jam.

Ctrl-b d does nothing — you launched claude-jam from inside another tmux, so the outer prefix eats the first Ctrl-b. Use Ctrl-b Ctrl-b d.

a or d lands in the message instead of answering — the single keys are armed only on an empty input line, so a message starting with d can never deny anybody. Esc re-arms them. A guest never has them at all.

The first run in a directory

Nothing happens after claude-jam host — in a directory Claude Code has never run in, claude shows "do you trust the files in this folder?". The daemon answers it automatically before its first injection (moving off the "No, exit" default first), and waits up to 30 s for it. If it is still stuck, look:

tmux -L claude-jam-<port> attach -t <name>:claude

answer it, Ctrl-b d to come back.

command -v claude prints alias text, not a path — that is zsh. Ask for the binary: whence -p claude. And check what comes back: on a machine running cmux, whence -p claude can be a shim in $TMPDIR rather than claude. $HOME/.local/bin/claude is usually the real one, and is claude-jam's own first choice. JAM_CLAUDE=<path> pins it.

Messages and prompts

"couldn't confirm your message reached claude" — it was kept, byte for byte, and nothing was retyped or wiped. /outbox lists what is kept, /retry sends the newest again, gets the text back into your box.

A guest's /command did nothing — it is waiting for the host's /allow-cmd, or it hit the hard host-only list (/exit, /clear, /resume), or it expired after 2 minutes. The exact reason went to that person's own client as a ! line.

A guest's file or /export did nothing — waiting for /accept-file / /allow-export, or expired, or refused: a name with a path in it, over 20 MB (files) / 50 MB (transcript and offers), or a second one while the first is still in flight.

/answer says "nothing is waiting for a permission answer" — claude is not asking right now. The in the status row is the moment it works.

/answer says it cannot read numbered options — that prompt is not one the relay can drive (or what is on screen is not a prompt at all). The host answers that one with F3.

/answer says "claude's screen changed after you asked" — the prompt was answered or replaced between the request and the host's approval, so nothing was typed. Look at the screen and ask again.

A guest ran /cost, /status or /context and nobody was asked — correct. Exactly those three, exactly bare, are read-only and need no approval. /cost --json asks like anything else.

Nothing ever asks for permission at all — the host's Claude Code is running with "defaultMode": "bypassPermissions" or --dangerously-skip-permissions. There is then no prompt to relay and no approval anywhere. See Security Model.

Replies stop with a spend-limit message — the host's Claude account hit its usage limit. They can restart with --config-dir pointing at another profile.

Files

/paste says it is macOS-only — it is: it reads a PNG off the mac clipboard (pngpaste when installed, otherwise the built-in osascript). Elsewhere, save the image and /send <path>.

/diff says "not inside a git repository" — the host's --cwd is not in one. /files still works; it reports what tool calls named rather than what git sees.

/diff says "no unstaged changes" — the work is committed or staged. /diff shows only the unstaged working tree, capped at 120 lines.

/files says "no files yet" — nothing has read or edited a file that a tool call named. A file changed by a shell command inside a Bash call never appears there — that is what /diff is for.

Connection

⚠ stale 40s in the status row — no heartbeat pong for more than a couple of 30-second intervals. That link is degraded or gone; the client reconnects on its own, in tiers.

--funnel cannot start: … — the message names the one missing thing. Funnel needs the funnel node attribute granted for the tailnet in Access Controls, a connected Tailscale, and a CLI claude-jam can reach (--funnel-cli <path>). On macOS the App Store build of Tailscale.app is sandboxed and cannot change funnel configuration at all — its CLI answers The Tailscale GUI failed to start … (Tailscale.CLIError error 3.). The standalone build from tailscale.com can. --tunnel is always the fallback.

Sessions that will not go away

claude-jam end refuses — claude-jam ends only a session it created and can prove it created. The message names what did not line up. tmux kill-session -t <name> is the human's own call, not claude-jam's, and it should be an exact name (-t =name), never a pattern.

claude-jam sessions does not show a jam that is clearly running — it was started before v0.18, so it has no marker and no session.json; claude-jam treats it as none of its business. End that one with tmux kill-session -t <name>. A jam created by 0.18.0 is recognised — its older @jam-owned marker is still read.

A jam shows ! no-daemon — the tmux session is up but nothing answers on its port. claude-jam end <name> clears it out.

A jam shows ! orphan — the tmux session is gone and only the state directory is left. claude-jam clean removes exactly those, after listing what will go.

A jam shows ! incomplete — the state directory is claude-jam's own claude-jam-<port> but holds no session.json, so there is no session name in it and nothing that could ever authorise an end. That is what a start looks like when it died between making the directory and claiming a session, and the directory can still be holding a token.json. claude-jam clean removes those too, once nothing is listening on the port. (While something IS listening the row reads no-session instead and clean leaves it alone — a daemon started with --daemon legitimately has no session.json of its own.)

A jam shows ! no-session — no tmux session, but something still holds that port. claude-jam leaves it alone, because whatever holds the port is not its to remove.

A jam shows foreign — that name is somebody else's tmux session. It is shown and never touched.

Host wants their client back after closing itclaude-jam host --attach, or the claude-jam join ws://127.0.0.1:<port> … --host line the launcher printed.

Host wants a clean restartclaude-jam end, then claude-jam host … again; --resume <session-id> keeps the conversation.

Everybody's client said <Host> ended the jam and exited — it did end, on purpose. There is nothing to reconnect to; the host starts a new one.

Still stuck

MANUAL.md in the repo is the manual claude itself is given, so inside a jam you can simply ask: "how does this jam work?" — and get an accurate answer rather than a guess.

Next

Install · Hosting a Jam · Joining a Jam · Architecture

Clone this wiki locally