The Coder engine stopped using tmux — packages/browser-runner/src/coding/headless.ts:29:
The coding engine — Claude Code driven through its structured stream-json interface, NOT a
scraped tmux TUI … Survives a runner restart without tmux … routes and console keep working
unchanged — only the guts move from tmux to structured JSON.
It spawns a child process (spawn(this.cmdBin, …), :241/:321) and tracks it via
proc.exitCode/signalCode. Nothing in that path calls createSession from coding/tmux.ts.
tmux is still genuinely used — but only by the terminal-operator family (coding/terminal.ts,
server.ts:351-363), i.e. the tmux / Terminal / kitty / iTerm2 Operator agents. Those create real
sessions. Repo Coders do not.
The surface around the coding path was never cleaned up, and it now actively misleads.
What's stale
1. A session name that names nothing. headless.ts:115 still computes
this.sessionName = pags-${clientType}-${id}, which is persisted to coding_sessions.tmux_session
and surfaced through the API and console. On a live instance today: 13 sessions each recording
pags-claude-csess_…, and zero such tmux sessions have ever existed. A user who tries
tmux attach -t pags-claude-csess_… — the obvious thing to do with a name like that — gets
"session not found" and reasonably concludes their engine is broken.
2. Diagnostics measure something structurally always zero. coding_diagnostics returns
tmux: { trackedSessions, orphanedSessions, tmuxTotal, pagsTmuxTotal }. Real output from an
instance whose runner is healthy:
tmux: { trackedSessions: 0, orphanedSessions: [], tmuxTotal: 4, pagsTmuxTotal: 0 }
pagsTmuxTotal: 0 is not a symptom — it can never be anything else for a coding agent. tmuxTotal: 4
counts the user's own unrelated tmux sessions. This is the panel someone opens because something
is wrong, and it points them at a false cause.
3. A "Kill tmux" control that cannot help. POST /:instanceId/coding/kill-tmux (routes/coding.ts:1413)
proxies to the runner's /coding/kill-tmux. For a coding session there is no tmux to kill, so the
diagnostics panel offers a cleanup action that structurally no-ops for the sessions it is shown
beside.
4. The docs describe the old architecture. pags/CLAUDE.md still says "Engine = the actual CLI
running in a tmux pane on the user's machine", "tmux addressed by session name (not :0.0)",
and "The runner reattaches to existing tmux coding sessions on reconnect". The last one is
wrong in a load-bearing way: reattach works by re-spawning with --resume <claudeSessionId> from
~/.claude, which is why it survives a restart without tmux.
5. Stale comments in live code. lib/coding-store.ts:444 explains the driver-claim expiry in
terms of "the RUNNER reports no tmux". The reconciler is fine — trackedIds comes from the runner's
in-memory tracked[] registry (routes/coding.ts:1495-1497), not from tmux — but the comment sends
the next reader down the wrong path. (Verified: not a functional bug.)
Suggested shape
Why bother
This is what made a maintainer (and the product owner) ask "are Coder agents connected to tmux?"
and start debugging in the wrong place. The names, the diagnostics panel and the docs all assert an
architecture the code deliberately replaced.
Verified, and the three forks resolved
Every claim above checks out against the code, plus the live-data assertion: this machine has 4 tmux sessions and zero named pags-* — one is tmux-operator-runner, i.e. the operator family, exactly as described.
Two findings that decide the open questions:
Nothing consumes the stale surfaces. No console or coder-web code reads tmuxSession, pagsTmuxTotal, or calls kill-tmux. The only consumer of /coding/diagnostics at all is MCP coding_diagnostics (workers/mcp/src/instance-tools/coding.ts:27), which passes the JSON through. So all three forks lean the same way — remove — and none is a breaking change for a real caller.
kill-tmux is NOT a no-op, contrary to point 3. Its no-args branch calls runner.coding.closeAll() (server.ts:237), which really does close the tracked engine processes. Only the tmux targeting is dead. Retiring the route wholesale would delete the one working "close everything" escape hatch — it needs re-scoping, not removal.
Decisions
sessionName → engineLabel, and drop the pags- prefix. The prefix is what makes a user type tmux attach -t pags-claude-…. In the coding path this value addresses nothing — runtime.ts:193/211 only reports it. The persisted column coding_sessions.tmux_session stays (a D1 column rename is a table rewrite for a cosmetic gain); it gets a comment saying it holds an engine label under a legacy name, and stops being surfaced as tmuxSession in API responses.
- Remove the tmux block from the coding diagnostics rather than scoping it.
trackedSessions/orphanedSessions/pagsTmuxTotal are structurally zero for coding agents, and tmuxTotal counts the user's unrelated sessions. The operator agents have their own endpoints and are untouched.
- Re-scope
kill-tmux to what works. Runner keeps the path (an older runner must still answer a newer API — version skew), with the tmux targeting stripped so it just closes tracked coding sessions. The API gains the honest name POST /:instanceId/coding/close-sessions; kill-tmux stays as a deprecated alias since removing a route nothing calls buys nothing.
Also needed, not in the original scope
coding/tmux.ts also hosts ensureRepo (:166, does the git clone) and sanitizeSessionName (:153), both imported by runtime.ts:6 and neither anything to do with tmux. The file cannot simply shrink to nothing; those two move out first.
The docs pointer at the end should not say #208/#211 — those are the two docs tickets whose numbers unrelated commits have been misusing, so it sends the reader to the wrong place.
Acceptance criteria
The Coder engine stopped using tmux —
packages/browser-runner/src/coding/headless.ts:29:It spawns a child process (
spawn(this.cmdBin, …),:241/:321) and tracks it viaproc.exitCode/signalCode. Nothing in that path callscreateSessionfromcoding/tmux.ts.tmux is still genuinely used — but only by the terminal-operator family (
coding/terminal.ts,server.ts:351-363), i.e. the tmux / Terminal / kitty / iTerm2 Operator agents. Those create realsessions. Repo Coders do not.
The surface around the coding path was never cleaned up, and it now actively misleads.
What's stale
1. A session name that names nothing.
headless.ts:115still computesthis.sessionName = pags-${clientType}-${id}, which is persisted tocoding_sessions.tmux_sessionand surfaced through the API and console. On a live instance today: 13 sessions each recording
pags-claude-csess_…, and zero such tmux sessions have ever existed. A user who triestmux attach -t pags-claude-csess_…— the obvious thing to do with a name like that — gets"session not found" and reasonably concludes their engine is broken.
2. Diagnostics measure something structurally always zero.
coding_diagnosticsreturnstmux: { trackedSessions, orphanedSessions, tmuxTotal, pagsTmuxTotal }. Real output from aninstance whose runner is healthy:
pagsTmuxTotal: 0is not a symptom — it can never be anything else for a coding agent.tmuxTotal: 4counts the user's own unrelated tmux sessions. This is the panel someone opens because something
is wrong, and it points them at a false cause.
3. A "Kill tmux" control that cannot help.
POST /:instanceId/coding/kill-tmux(routes/coding.ts:1413)proxies to the runner's
/coding/kill-tmux. For a coding session there is no tmux to kill, so thediagnostics panel offers a cleanup action that structurally no-ops for the sessions it is shown
beside.
4. The docs describe the old architecture.
pags/CLAUDE.mdstill says "Engine = the actual CLIrunning in a tmux pane on the user's machine", "tmux addressed by session name (not
:0.0)",and "The runner reattaches to existing tmux coding sessions on reconnect". The last one is
wrong in a load-bearing way: reattach works by re-spawning with
--resume <claudeSessionId>from~/.claude, which is why it survives a restart without tmux.5. Stale comments in live code.
lib/coding-store.ts:444explains the driver-claim expiry interms of "the RUNNER reports no tmux". The reconciler is fine —
trackedIdscomes from the runner'sin-memory
tracked[]registry (routes/coding.ts:1495-1497), not from tmux — but the comment sendsthe next reader down the wrong path. (Verified: not a functional bug.)
Suggested shape
tmuxSessionfrom the coding-session model, or rename it to what it is (an engine/sessionlabel). If the column stays for migration reasons, stop surfacing it in the API and console.
coding_diagnosticsfor coding agents, or scope it to theterminal-operator agents where it means something.
kill-tmuxon the coding path.CLAUDE.mdand the published docs to describe the stream-json engine and--resumereattach. Docs [docs] Reconcile runtime docs with relay-only
pags up#208/[docs] Update Coder docs for runner-node picker, engines, and package paths #211 already cover runtime/Coder doc drift — this belongs with them.Why bother
This is what made a maintainer (and the product owner) ask "are Coder agents connected to tmux?"
and start debugging in the wrong place. The names, the diagnostics panel and the docs all assert an
architecture the code deliberately replaced.
Verified, and the three forks resolved
Every claim above checks out against the code, plus the live-data assertion: this machine has 4 tmux sessions and zero named
pags-*— one istmux-operator-runner, i.e. the operator family, exactly as described.Two findings that decide the open questions:
Nothing consumes the stale surfaces. No console or coder-web code reads
tmuxSession,pagsTmuxTotal, or callskill-tmux. The only consumer of/coding/diagnosticsat all is MCPcoding_diagnostics(workers/mcp/src/instance-tools/coding.ts:27), which passes the JSON through. So all three forks lean the same way — remove — and none is a breaking change for a real caller.kill-tmuxis NOT a no-op, contrary to point 3. Its no-args branch callsrunner.coding.closeAll()(server.ts:237), which really does close the tracked engine processes. Only the tmux targeting is dead. Retiring the route wholesale would delete the one working "close everything" escape hatch — it needs re-scoping, not removal.Decisions
sessionName→engineLabel, and drop thepags-prefix. The prefix is what makes a user typetmux attach -t pags-claude-…. In the coding path this value addresses nothing —runtime.ts:193/211only reports it. The persisted columncoding_sessions.tmux_sessionstays (a D1 column rename is a table rewrite for a cosmetic gain); it gets a comment saying it holds an engine label under a legacy name, and stops being surfaced astmuxSessionin API responses.trackedSessions/orphanedSessions/pagsTmuxTotalare structurally zero for coding agents, andtmuxTotalcounts the user's unrelated sessions. The operator agents have their own endpoints and are untouched.kill-tmuxto what works. Runner keeps the path (an older runner must still answer a newer API — version skew), with the tmux targeting stripped so it just closes tracked coding sessions. The API gains the honest namePOST /:instanceId/coding/close-sessions;kill-tmuxstays as a deprecated alias since removing a route nothing calls buys nothing.Also needed, not in the original scope
coding/tmux.tsalso hostsensureRepo(:166, does the git clone) andsanitizeSessionName(:153), both imported byruntime.ts:6and neither anything to do with tmux. The file cannot simply shrink to nothing; those two move out first.The docs pointer at the end should not say #208/#211 — those are the two docs tickets whose numbers unrelated commits have been misusing, so it sends the reader to the wrong place.
Acceptance criteria
pags-*value that looks like a tmux target./coding/diagnosticsreturns nothing tmux-shaped for a coding agent; the operator agents' tmux endpoints are unchanged and still work.ensureRepo/sanitizeSessionNameno longer live in a file named tmux.coding-store.ts:444no longer explains the claim expiry in terms of tmux.pags/CLAUDE.mddescribes the stream-json engine and--resumereattach, not tmux panes.