Skip to content

[bug] A 4-second retry budget kills long runs — Heartfull died at iteration 2 of 50 on a runner blip the runner itself recovers from #341

Description

@serge-ivo

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions