Skip to content

[bug] The Terminal tab learns the runner is gone only by failing — and blanks that error every 4s, so a phone shows an empty pane and a flicker #378

Description

@serge-ivo

Reported

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

  1. 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 the
    block 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.
  3. 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.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingfrontendFrontend / UI work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions