What happened
Heartfull Coder (f8ddc272), 2026-08-06. Objective: work through all 30 open issues in
HeartFull-online/platform, P1 first, maxIterations: 50.
02:34:29 run f3991cac starts
02:34:36 Loop → engine (step 1) ← runner connected, engine driven
02:35:25 Loop → engine (step 2) ← still fine
02:39:08 Loop stopped (failed) — "run error: No runner connected — run `pags up`"
279 seconds, iteration 2 of 50. The runner was demonstrably attached — two steps reached the
engine — and then dropped. A four-and-a-half-minute setup for a multi-hour job, gone.
Note the run did not stall or wait: a missing runner is mapped straight to a terminal outcome
(workflows/coding-session.ts:300):
const noRunner: CodingResult = { outcome: "failed", detail: "No coding runner connected. Start it with: pags up", steps: 0 };
The retry budget is shorter than the runner's own reconnect
coding-session.ts:311:
const retry = { retries: { limit: 2, delay: "2 seconds", backoff: "constant" }, timeout: "3 minutes" };
Two retries, two seconds apart — a ~4 second tolerance for the runner being absent.
Meanwhile the runner's relay client reconnects with exponential backoff capped at 30 seconds
(packages/cli/src/commands/runner/relay.ts). So the workflow gives up while the runner is still
in its own retry loop, for a disconnect the runner is about to recover from by itself. A laptop
lid, a wifi handover, or a pags up restart all exceed 4 seconds comfortably.
The platform already knows how to do this
And the message names the wrong remedy
"run pags up" — but pags up was running; it dropped and would have come back. Same pattern as
#271 (a 409 appending "and run pags up" while the runner was up) and #321 ("no github access" for
an unknown owner). An error that prescribes a fix the user has already applied trains them to
distrust the message.
Proposal
Treat a runner disconnect as a pause, not a failure:
This matters most for exactly the runs where it hurts: maxIterations: 50 over 30 issues is hours
of work, and any single blip in that window currently ends it.
Verification
- A run survives a 30-second runner disconnect and resumes at the same iteration.
- The thread shows the pause and the recovery.
- A runner that never returns fails after the window with an accurate message.
- A run that fails for a real reason still fails immediately — this must not slow down genuine errors.
What happened
Heartfull Coder (
f8ddc272), 2026-08-06. Objective: work through all 30 open issues inHeartFull-online/platform, P1 first,maxIterations: 50.279 seconds, iteration 2 of 50. The runner was demonstrably attached — two steps reached the
engine — and then dropped. A four-and-a-half-minute setup for a multi-hour job, gone.
Note the run did not stall or wait: a missing runner is mapped straight to a terminal outcome
(
workflows/coding-session.ts:300):The retry budget is shorter than the runner's own reconnect
coding-session.ts:311:Two retries, two seconds apart — a ~4 second tolerance for the runner being absent.
Meanwhile the runner's relay client reconnects with exponential backoff capped at 30 seconds
(
packages/cli/src/commands/runner/relay.ts). So the workflow gives up while the runner is stillin its own retry loop, for a disconnect the runner is about to recover from by itself. A laptop
lid, a wifi handover, or a
pags uprestart all exceed 4 seconds comfortably.The platform already knows how to do this
isTransientInfraErrorexists and is applied to DO resets — "a durable Workflow shouldretry/resume through it (that's the point of Workflows surviving deploys)". A runner disconnect
is the same category and is not covered by it.
down: "Wide on purpose: these retries wait out someone else's outage, which resolves in
minutes-to-hours." The coding loop waits four seconds for the same class of event.
And the message names the wrong remedy
"run
pags up" — butpags upwas running; it dropped and would have come back. Same pattern as#271 (a 409 appending "and run
pags up" while the runner was up) and #321 ("no github access" foran unknown owner). An error that prescribes a fix the user has already applied trains them to
distrust the message.
Proposal
Treat a runner disconnect as a pause, not a failure:
handoff (
STALE_DRIVER_MSis 15 min) and comfortably outlasts the 30s reconnect cap.visible rather than looking hung — the [bug] Navigating back loses 'working on it' — the run is still going and the API already reports it #252 lesson.
is usually still there.
pags up".This matters most for exactly the runs where it hurts:
maxIterations: 50over 30 issues is hoursof work, and any single blip in that window currently ends it.
Verification