Skip to content

[bug] Stop on a loop looks like it does nothing — the server records the cancel and no UI ever reads cancelRequested #376

Description

@serge-ivo

Reported

"I can't stop the loop for Heartfull."

The server accepted the stop. Nothing in the UI ever says so.

Live state of the run right now (f8ddc272, run 252061dc):

status:          running
cancelRequested: true      ← the stop WAS recorded
iteration:       32 / 50
startedAt:       2026-08-07T07:58:02Z
lastProgressAt:  2026-08-07T09:06:31Z

So requestCancel worked: agent_loop_runs.cancel_requested = 1. The API returns it — it is right
there in check_instance_loop's payload as cancelRequested.

No UI reads it. git grep cancelRequested across store/console/src and
agents/coder/web/src returns nothing.

The consequence, in LoopRunsSection.tsx:

  • pressing Stop sets a transient local message — "Stopping — the current step will finish first."
    (:87), which is honest but is component state and vanishes on the next poll, re-render or
    navigation;
  • the row itself renders from r.status === "running" alone (:108, :125), so it keeps showing
    · running and keeps offering a live Stop button.

So: press Stop → a message flashes → the row still says running → press Stop again → same. The user
correctly concludes it does not work. The flag is set, re-set, and re-set; the UI is simply not
looking at it.

Why the wait is long enough to matter

Cancellation is cooperative by design and the reasoning is sound (agent-loop-store.ts:160-166):
the flag is read at the top of each iteration (workflows/agent-loop.ts:77) so the in-flight
step settles its spend, because killing mid-step strands a budget reservation against the shared
tree pool.

But the in-flight step is act-${iteration} — handing an instruction to a Coder that drives Claude
Code through a real task. That is minutes, not seconds. This run has been 6m45s past its last
recorded progress and is presumably still inside iteration 32.

A cooperative cancel with a multi-minute settling window is fine. A cooperative cancel with a
multi-minute window and no persistent acknowledgement is indistinguishable from a broken button.

Fix

  1. Render the pending state. When cancelRequested && status === "running", show Stopping…
    instead of running, and disable the Stop button. The data is already on the wire; this is a
    read, not a new endpoint.
  2. Say what it is waiting for — "finishing the current step" — so a multi-minute wait reads as
    the documented behaviour rather than a hang. work-report.ts:87 already composes this sentence
    for the work report ("a cancel has been requested"); the console just never got it.
  3. Consider a second press meaning something. Once a cancel is pending, a further Stop could
    offer a hard abort. Not obviously right — it reintroduces the stranded-reservation problem the
    cooperative design exists to avoid — so this needs a decision, not just an implementation.

Also worth noting from the same data

Three of this instance's six runs died with WorkflowInternalError: Attempt failed due to internal workflows error, and two more with the $50.00 daily limit (#343). The run list is mostly failures.
Not this issue, but the same instance is the evidence for several open ones.

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