Skip to content

fix(core): let answers land on a failed-then-resumed run - #10

Merged
sergeyzenchenko merged 1 commit into
mainfrom
fix/answer-after-resume
Sep 4, 2026
Merged

fix(core): let answers land on a failed-then-resumed run#10
sergeyzenchenko merged 1 commit into
mainfrom
fix/answer-after-resume

Conversation

@yporodko

@yporodko yporodko commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

The bug

Engine.answer() decides whether a run is terminal by folding the whole journal, but nothing in that fold clears a historical run.failed when the run is resumed. Once a run has failed once (e.g. a step timeout), every subsequent gate answer is refused forever with:

run <id> is already failed — resume it before answering

— including after resuming, which is exactly what the error tells you to do. A resumed step that parks on a permission gate then sits unanswerable until it hits its own step timeout, failing the run again:

step timeout → run.failed → resume → step parks on gate → answer refused → step timeout → …

A guaranteed deadlock loop for any failed-then-resumed run whose steps use risk-gated tools.

Reproduction (observed in the wild)

Two long agent workflows (design agents using gated Bash) hit a 30-minute step timeout, were resumed, parked on their next permission gate, and could not be answered by CLI (weft answer) or daemon (POST /api/runs/:id/answer) — both raised the terminal-run guard — until the step timed out again. With this patch applied, both runs accepted their gate answers on the next resume and ran to completion.

The fix

In both fold sites (the live-engine path and the suspended-run path), a run.status: "executing" event — which resume appends — resets the terminal flag, so the answer guard reflects the run's actual liveness rather than its history. 9 lines, comments included.

Related observation (possibly its own issue)

Time spent parked on a human gate counts against the step's timeout. That is what turned this bug into a hard deadlock, and even without the bug it means slow human approvals can kill an otherwise healthy step. Splitting gate-wait time out of step execution time may be worth considering separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DSa5GP2q5WNtNJ1Mt4V2z4

The answer path decides whether a run is terminal by folding the whole
journal, but nothing in that fold clears a historical run.failed when the
run is resumed. Once a run has failed once (e.g. a step timeout), every
subsequent gate answer is refused with "run is already failed — resume it
before answering" forever — including after resuming. A resumed step that
parks on a permission gate then sits unanswerable until it hits its own
step timeout, which fails the run again: a guaranteed deadlock loop
(timeout -> resume -> gate -> unanswerable -> timeout).

Fix: in both fold sites (live-engine and suspended-run), a
run.status: "executing" event — which resume appends — resets the terminal
flag, so the answer guard reflects the run's actual liveness instead of its
history.

Observed in the wild: two long agent workflows deadlocked exactly this way;
with this patch both accepted their gate answers and ran to completion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DSa5GP2q5WNtNJ1Mt4V2z4
@sergeyzenchenko
sergeyzenchenko merged commit 371898a into main Sep 4, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants