A "Runs on" tile says "Attached · online" for a machine nothing routes to, one line under "Status: Offline"
The owner's report was "why does it say runner offline if in settings the node is online". Both statements are on screen at the same time, in the same card, and the tile is the wrong one.
With the instance pinned to machine A and a live socket on machine B, RunnerPanel.tsx renders Status: Offline (pin-aware, correct) and then the "Runs on" grid, in which B's tile is green and labelled "Attached · online".
Mechanism
store/console/src/lib/runnerPanel.ts:112-114:
attached = m.instances.some(i => i.instanceId === instanceId && i.connected)
fed by workers/api/src/routes/terminals.ts:381-383, where connected = relayConnected(instanceId, node) — a bare socket probe with no pin. So "attached" is asserted about routing from a fact about connectivity. Nothing routed there: getBoundRunnerConn (lib/runner-client.ts:74-107) is pin-authoritative and never falls through.
The invariant that was supposed to catch this only covers the pinned tile
runnerPanel.ts's own header promises the tile is "derived from BOTH readings, so a disagreement is a test failure rather than a screenshot". But machinesToShow (:82-95) merges the two readings only for the synthesised tile, and returns [...machines] untouched whenever the pinned node already appears in the Terminals list. The test at store/console/src/lib/runnerPanel.test.ts:103-119 iterates only over the pinned node's tile (machinesToShow([], "laptop", …)).
A tile for a non-pinned node holding a live socket is untested. Grepped — that is the gap.
The Terminals page (/v1/terminals/nodes) shares the same rows and the same blindness.
The one surface that told the truth
diagnoseAttachment (routes/instances.ts:860-866) renders, correctly: "This agent is pinned to , which isn't connected. is connected — set 'Runs on' to (or Automatic)". It sits directly above the tile that contradicts it.
Second, independent divergence — same family, different file
workers/api/src/routes/coding-shared.ts:53-54:
export async function getSessionRunnerConn(env, instanceId, uid, session) {
return getRunnerConn(env, instanceId, uid, session.runnerNode ?? null);
}
Routes by the session's stamped node — no pin, no alias, and no relay live-check: only status != 'offline' on a column that is never cleared on disconnect. /capture derives runnerConnected from it (coding.ts:223), and resolveRunnerOnline gives a live session's capture priority over the relay answer (if (hasActiveSessions && capture === false) return false). So a session stamped to a machine the owner has left makes the Coding tab report offline while the header dot reports online.
f8ddc272… held exactly such a session — csess_c960d431…, runnerNode: "Sergeys-Mac-mini.local", active from 2026-08-11 21:32:49 until 07:44:51 on 2026-08-12, i.e. through the incident. /coding/browse (getDefaultRunnerConn) and /coding/diagnostics bypass the pin the same way; coding-repos.ts:195-200 already documents this set as measured.
Split this into its own issue if it is cleaner to fix separately — it is filed here because it is the same defect class (a surface claiming attachment it never verified) and was found in the same trace.
Acceptance criteria
- A tile for a node the pin excludes does not claim "Attached". It may still show the machine as connected — connectivity and attachment are different facts and the label must say which it means.
machinesToShow's stated invariant holds for every tile, not only the synthesised one, and the test iterates a non-pinned node with a live socket.
- The Terminals page distinguishes the same two facts.
getSessionRunnerConn live-checks, or its callers stop treating its answer as authoritative for "is the runner online".
- A test with pin=A, live socket on B, asserting the card cannot render "Offline" and "Attached" for the same instance simultaneously.
Verified vs inferred
- Verified: every
file:line; the pin-blindness of terminals.ts:381; the test's coverage of only the pinned tile; the stamped session's node and its active window.
- Inferred: that the owner's screen showed the green tile at 07:44. He reported the contradiction and the code produces it deterministically in that state, but no screenshot was captured, and the pin has since moved so it cannot be reproduced without repinning.
A "Runs on" tile says "Attached · online" for a machine nothing routes to, one line under "Status: Offline"
The owner's report was "why does it say runner offline if in settings the node is online". Both statements are on screen at the same time, in the same card, and the tile is the wrong one.
With the instance pinned to machine A and a live socket on machine B,
RunnerPanel.tsxrendersStatus: Offline(pin-aware, correct) and then the "Runs on" grid, in which B's tile is green and labelled "Attached · online".Mechanism
store/console/src/lib/runnerPanel.ts:112-114:fed by
workers/api/src/routes/terminals.ts:381-383, whereconnected = relayConnected(instanceId, node)— a bare socket probe with no pin. So "attached" is asserted about routing from a fact about connectivity. Nothing routed there:getBoundRunnerConn(lib/runner-client.ts:74-107) is pin-authoritative and never falls through.The invariant that was supposed to catch this only covers the pinned tile
runnerPanel.ts's own header promises the tile is "derived from BOTH readings, so a disagreement is a test failure rather than a screenshot". ButmachinesToShow(:82-95) merges the two readings only for the synthesised tile, and returns[...machines]untouched whenever the pinned node already appears in the Terminals list. The test atstore/console/src/lib/runnerPanel.test.ts:103-119iterates only over the pinned node's tile (machinesToShow([], "laptop", …)).A tile for a non-pinned node holding a live socket is untested. Grepped — that is the gap.
The Terminals page (
/v1/terminals/nodes) shares the same rows and the same blindness.The one surface that told the truth
diagnoseAttachment(routes/instances.ts:860-866) renders, correctly: "This agent is pinned to , which isn't connected. is connected — set 'Runs on' to (or Automatic)". It sits directly above the tile that contradicts it.Second, independent divergence — same family, different file
workers/api/src/routes/coding-shared.ts:53-54:Routes by the session's stamped node — no pin, no alias, and no relay live-check: only
status != 'offline'on a column that is never cleared on disconnect./capturederivesrunnerConnectedfrom it (coding.ts:223), andresolveRunnerOnlinegives a live session's capture priority over the relay answer (if (hasActiveSessions && capture === false) return false). So a session stamped to a machine the owner has left makes the Coding tab report offline while the header dot reports online.f8ddc272…held exactly such a session —csess_c960d431…,runnerNode: "Sergeys-Mac-mini.local", active from 2026-08-11 21:32:49 until 07:44:51 on 2026-08-12, i.e. through the incident./coding/browse(getDefaultRunnerConn) and/coding/diagnosticsbypass the pin the same way;coding-repos.ts:195-200already documents this set as measured.Split this into its own issue if it is cleaner to fix separately — it is filed here because it is the same defect class (a surface claiming attachment it never verified) and was found in the same trace.
Acceptance criteria
machinesToShow's stated invariant holds for every tile, not only the synthesised one, and the test iterates a non-pinned node with a live socket.getSessionRunnerConnlive-checks, or its callers stop treating its answer as authoritative for "is the runner online".Verified vs inferred
file:line; the pin-blindness ofterminals.ts:381; the test's coverage of only the pinned tile; the stamped session's node and its active window.