You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On mobile: "No target selected" / "Start a terminal target below…" / "Select a terminal target to
capture its output." — and nothing happens. Only flickering of some error message.
Reproduced — the runner is not connected, and that is the only thing wrong
Live, on Heartfull (tmux) (cda75e28, surface tmux), calling the tab's own first tool:
terminal_list_targets → { success: false,
content: "No runner is connected for this agent — run `pags up` on the machine whose
terminal you want to control." }
That string is lib/connectors/terminal.ts:19, raised when getBoundRunnerConn finds no live socket.
So the state is ordinary and expected: the machine is not running pags up. Everything the user
described is the Terminal tab's handling of that ordinary state.
Supporting observation: GET …/runtime/status still reports status:"online" for two registered
nodes (Mac, RLs-MacBook-Air.local, runnerVersion 0.4.35), last seen 2026-08-07 20:41. That row
is the known-stale one — never cleared on disconnect (#238) — which is exactly why the route computes relay.connected live instead. Not a bug here; it matters below, because it is the reason the tab
must not read the row.
What is on screen, and why each part is unhelpful
Element
Source
Problem
No target selected
TmuxTab.tsx:333
True, and unactionable
Start a terminal target below or open tmux, kitty, or iTerm2 on the connected machine.
:337
Asserts "the connected machine"; every control that could start one is disabled
Select a terminal target to capture its output.
:366
There is nothing to select
No terminal targets found on the connected runner.
:307
Asserts a connected runner that does not exist — and is on the Targets view, which a phone is not showing (below)
flickering red text
:354-357
The only true statement in the tab, and it is unreadable
Defect 1 — the error is blanked at the start of every poll, so it flickers instead of standing
refreshTargets clears the error before attempting the call (TmuxTab.tsx:125), and capture
does the same (:145). The banner row is conditionally mounted (:354), so on each poll it unmounts, then remounts when the call fails — taking the layout with it.
The cadence makes it a loop rather than a one-off. tmuxBusy(targets, !!error) returns busy because
the last call failed, which pins the tier to activeMs: 4000 (:175). That is deliberate and correct
— lib/pollBusy.ts states the rule outright: "Believing something is OFFLINE counts as busy… backing
off then is how the tab stays showing an error long after the machine came back (#241)."
So two individually-correct decisions compose into the symptom: clear-on-attempt + poll-fastest-
while-failing = a message that blinks every 4 seconds and can never be read. Nothing is wrong with
either half; the error should simply be cleared on success, not on attempt.
Defect 2 — the tab has no connectivity input at all, and the diagnosed answer already exists
TmuxTab takes { instanceId } and nothing else (:10-12, mounted at lib/surfaces.tsx:190). SurfaceContext (surfaces.tsx:44-57) carries no runner state, so no surface can render an offline
state from the shell, even though the shell already holds one — InstanceDetail.tsx:153 keeps runnerOnline from /runtime/status and renders the header dot from it.
The server does not merely expose a boolean. GET /v1/instances/:id/runtime/status returns
(routes/instances.ts:783-799):
There is also a tested consumer to copy: agents/coder/web/src/runner-online.ts + runner-online.test.ts,
which combines relay.connected with live-session evidence so a stale offline verdict cannot outlive
its sessions (#241).
The Terminal tab uses none of it. It discovers connectivity only by failing a relay round-trip, and
then discards the reason 4 seconds later.
Defect 3 — why the phone gets the worst version of this
The honest signal — the header runner dot — is title-only on mobile (InstanceDetail.tsx:908-911),
and a tooltip does not exist on a touch device.
pags up is not a thing a phone can do, so even the correct advice needs different wording on the
surface most likely to be read on one.
Suggested fix
Clear the error on success, not on attempt (refreshTargets:125, capture:145). One-line class
of change; it alone converts a flicker into a readable message. Keep tmuxBusy's failure-is-busy
rule as-is — it is right.
Read connectivity directly. Poll /runtime/status and render the offline state from relay.connected + attachment (the reason and the remedy already come back), rather than
inferring it from a failed tool call. Either thread it through SurfaceContext — the shell already
has it, and every future runtime surface needs it — or reuse the runner-online.ts resolver.
Fix the copy for the offline case.:307 and :337 should not assert "the connected runner"
/ "the connected machine" when nothing is connected, and the empty state should read the same on
the Output view as on the Targets view.
Scope note
Not proposing a change to pollBusy, tmuxView or the #370 layout — all three behave as designed and
their reasoning holds. This is the missing offline state on a surface that only ever had an online one.
Reported
Reproduced — the runner is not connected, and that is the only thing wrong
Live, on
Heartfull (tmux)(cda75e28, surfacetmux), calling the tab's own first tool:That string is
lib/connectors/terminal.ts:19, raised whengetBoundRunnerConnfinds no live socket.So the state is ordinary and expected: the machine is not running
pags up. Everything the userdescribed is the Terminal tab's handling of that ordinary state.
Supporting observation:
GET …/runtime/statusstill reportsstatus:"online"for two registerednodes (
Mac,RLs-MacBook-Air.local,runnerVersion 0.4.35), last seen2026-08-07 20:41. That rowis the known-stale one — never cleared on disconnect (#238) — which is exactly why the route computes
relay.connectedlive instead. Not a bug here; it matters below, because it is the reason the tabmust not read the row.
What is on screen, and why each part is unhelpful
No target selectedTmuxTab.tsx:333Start a terminal target below or open tmux, kitty, or iTerm2 on the connected machine.:337Select a terminal target to capture its output.:366No terminal targets found on the connected runner.:307:354-357Defect 1 — the error is blanked at the start of every poll, so it flickers instead of standing
refreshTargetsclears the error before attempting the call (TmuxTab.tsx:125), andcapturedoes the same (
:145). The banner row is conditionally mounted (:354), so on each poll itunmounts, then remounts when the call fails — taking the layout with it.
The cadence makes it a loop rather than a one-off.
tmuxBusy(targets, !!error)returns busy becausethe last call failed, which pins the tier to
activeMs: 4000(:175). That is deliberate and correct—
lib/pollBusy.tsstates the rule outright: "Believing something is OFFLINE counts as busy… backingoff then is how the tab stays showing an error long after the machine came back (#241)."
So two individually-correct decisions compose into the symptom: clear-on-attempt + poll-fastest-
while-failing = a message that blinks every 4 seconds and can never be read. Nothing is wrong with
either half; the error should simply be cleared on success, not on attempt.
Defect 2 — the tab has no connectivity input at all, and the diagnosed answer already exists
TmuxTabtakes{ instanceId }and nothing else (:10-12, mounted atlib/surfaces.tsx:190).SurfaceContext(surfaces.tsx:44-57) carries no runner state, so no surface can render an offlinestate from the shell, even though the shell already holds one —
InstanceDetail.tsx:153keepsrunnerOnlinefrom/runtime/statusand renders the header dot from it.The server does not merely expose a boolean.
GET /v1/instances/:id/runtime/statusreturns(
routes/instances.ts:783-799):relay.connected— the live socket, pin-aware, resolved throughgetLiveRuntime([bug] /runtime/status reports the stale default row's node, so the console's connected dot can be flatly wrong #238) preciselybecause the stored
statuscolumn lies; andattachment—diagnoseAttachment([bug] A rejected relay socket retries forever and the console shows only an unexplained amber dot #237), whose stated purpose is "Say WHY when it isn't attached…the CLI knew the reason and the remedy and printed both to a terminal nobody was watching."
There is also a tested consumer to copy:
agents/coder/web/src/runner-online.ts+runner-online.test.ts,which combines
relay.connectedwith live-session evidence so a stale offline verdict cannot outliveits sessions (#241).
The Terminal tab uses none of it. It discovers connectivity only by failing a relay round-trip, and
then discards the reason 4 seconds later.
Defect 3 — why the phone gets the worst version of this
DEFAULT_TMUX_VIEW = "output"(lib/tmuxView.ts), correctly chosen by [bug] Terminal output is invisible on mobile — TmuxTab stacks eight blocks in one column and the output pane is the only one that yields #370 so the pane is not theblock that gets hidden. Consequence here: the one empty-state that names the runner
("No terminal targets found on the connected runner") lives on the Targets view, so a phone
never sees it. It gets the two strings that say "select a target" instead.
title-only on mobile (InstanceDetail.tsx:908-911),and a tooltip does not exist on a touch device.
pags upis not a thing a phone can do, so even the correct advice needs different wording on thesurface most likely to be read on one.
Suggested fix
refreshTargets:125,capture:145). One-line classof change; it alone converts a flicker into a readable message. Keep
tmuxBusy's failure-is-busyrule as-is — it is right.
/runtime/statusand render the offline state fromrelay.connected+attachment(the reason and the remedy already come back), rather thaninferring it from a failed tool call. Either thread it through
SurfaceContext— the shell alreadyhas it, and every future runtime surface needs it — or reuse the
runner-online.tsresolver.:307and:337should not assert "the connected runner"/ "the connected machine" when nothing is connected, and the empty state should read the same on
the Output view as on the Targets view.
Scope note
Not proposing a change to
pollBusy,tmuxViewor the #370 layout — all three behave as designed andtheir reasoning holds. This is the missing offline state on a surface that only ever had an online one.
Files:
store/console/src/tabs/TmuxTab.tsx,store/console/src/lib/surfaces.tsx,store/console/src/pages/InstanceDetail.tsx,agents/coder/web/src/runner-online.ts,workers/api/src/routes/instances.ts,workers/api/src/lib/connectors/terminal.ts.