Skip to content

fix(tasks): give resume the worker it never had (#1005) - #1020

Merged
frankbria merged 2 commits into
mainfrom
fix/1005-web-resume-worker
Aug 1, 2026
Merged

fix(tasks): give resume the worker it never had (#1005)#1020
frankbria merged 2 commits into
mainfrom
fix/1005-web-resume-worker

Conversation

@frankbria

Copy link
Copy Markdown
Owner

Closes #1005.

The bug

POST /tasks/{id}/resume called runtime.resume_run() and returned. That flips the run to RUNNING and the task to IN_PROGRESS — and nothing executes it. start spawns a background thread for exactly this; resume never had an equivalent.

The task is then wedged: it holds an active run, so start_task_run() rejects any attempt to start it again, and nothing is driving the run it does have. The only way out was editing the database.

Every BLOCKED task resumed from the web UI hits this — stall blockers, agent-escalated blockers, and (as of #911) cost-cap blockers, which give users a routine reason to block and resume.

The CLI was worse

cf work resume had the same gap and no escape at all. It never executed, and afterwards cf work start rejects the active run — so no command existed that would drive the task.

Fix

The two endpoints drifted, so the thread body is now one helper, _spawn_agent_worker, called by both. Resume inherits the #722 fail_run/ErrorEvent recovery for free — a resumed run whose agent fails up front (missing API key, unknown provider) now resets to FAILED instead of stranding.

resume's execute defaults to True, unlike start's False. web-ui/src/lib/api.ts:437 posts here with no query params, so any other default would leave the bug in place for the only real caller — and a resumed run that deliberately does no work has no use case. execute=false keeps the old flip-only behaviour for anyone who wants it.

cf work resume now runs the agent in the foreground by default, matching cf work start --execute, with --no-execute to opt out (and a message pointing at --execute when you do).

Core is untouched: runtime.resume_run stays headless and does not learn about threads or event publishers.

Testing

Asserted on outcome — did a worker actually run the resumed run — not on the endpoint returning 200, which it always did.

  • test_resume_actually_runs_the_agent — a worker runs at all
  • test_the_worker_gets_the_resumed_run — the same run resume returned, not a fresh one
  • test_resume_does_not_wedge_the_task — the user-visible symptom: after a failing resume the task is retryable and start works again
  • test_execute_false_keeps_the_flip_only_behaviour
  • test_the_default_executes_because_the_web_ui_sends_no_params
  • test_start_still_works_through_the_shared_helper — this one passes on main, so it pins that extracting the helper does not regress start
  • CLI: executes by default, --no-execute opts out

tests/ui/ + tests/cli/: 1168 passed, ruff clean.

`POST /tasks/{id}/resume` called runtime.resume_run() and returned. That flips
the run to RUNNING and the task to IN_PROGRESS with nothing executing it — and
because the run is then *active*, start_task_run() refuses to start the task
again. The task is wedged until someone edits the database. Every BLOCKED task
resumed from the web UI hits this.

`start` already spawns a background thread for exactly this; resume had no
equivalent. The two endpoints drifted, so the thread body is now one helper
(_spawn_agent_worker) called by both — resume inherits the #722 fail_run /
ErrorEvent recovery for free.

resume's `execute` defaults to True, unlike start's False: web-ui/src/lib/api.ts
posts here with no query params, so any other default would leave the bug in
place for the only real caller, and a resumed run that deliberately does no work
has no use case.

`cf work resume` had the same gap and no escape at all — it never executed, and
afterwards `work start` rejects the active run, so no command could drive the
task. It now runs the agent in the foreground by default, with --no-execute to
opt out (and a message pointing at --execute when you do).

Tests assert on outcome — did a worker actually run the resumed run — not on the
endpoint returning 200, which it always did.
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @frankbria's task in 3m 52s —— View job


Bug-hunting review complete

  • Gather PR context (gh pr view, gh pr diff, existing comments)
  • Read changed files and surrounding code in the checked-out repo
  • Trace callers/callees to confirm each suspicious hunk
  • Post inline findings (concrete defects only)
  • Post summary comment

1 defect found (1 inline comment + summary posted):

The web-endpoint change (shared _spawn_agent_worker) is sound.

Comment thread codeframe/cli/app.py Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

GLM bug-hunting review — 1 defect

severity file:line finding
major codeframe/cli/app.py:2697 cf work resume re-wedges the run on an up-front execute_agent failure (e.g. missing/revoked API key): resume_run already flipped the run to RUNNING, the raised ValueError escapes to the outer handler and exits, but the run is never failed — so start then rejects it on "already has an active run". The web worker recovers via fail_run (#722); the CLI path has neither that nor work_start's upfront key validation, and the raising case is untested (the CLI recorder returns a fake state).

Scope: concrete defects only (logic errors, error-path/crash, data loss, races, resource leaks). Style, architecture, naming, and test coverage are out of scope — covered by the general review.

No other concrete defects found. The web-endpoint extraction (_spawn_agent_worker) is sound: resume_run → RUNNING → worker runs the same run; up-front failures reset to FAILED via fail_run; double-resume is guarded by core (Run is not blocked). One inline suggestion posted.

…view)

The bot review caught the CLI path re-introducing the exact wedge this change
fixes. `resume_run` has already flipped the run to RUNNING and the task to
IN_PROGRESS; `execute_agent` then raises before its own try for a missing key,
the outer `except ValueError` printed and exited, and the run stayed RUNNING
with no worker — so `work start` refused the task and only `work stop` got you
out. The web worker recovers from this via fail_run (#722); the CLI had no
equivalent.

It was uncovered because the CLI tests' recorder returns a fake state instead
of raising, so nothing exercised the raising path. Now it does.

Writing that fix also surfaced a NameError: cli/app.py defines no module
logger, so the `logger.debug` I first put in the nested recovery handler would
have masked the real error with its own exception. Swallowed with a comment
instead, and a second test asserts the real error still reaches the user when
recovery itself fails.
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Claude finished @frankbria's task in 4m 53s —— View job


GLM bug-hunting review (re-review after 1a3ddbb)

Precision review — concrete defects only (logic errors, error/crash paths, security, data loss, races, API/contract breaks, resource leaks). Style/architecture/naming/coverage are out of scope (covered by the general review).

  • Gather PR context (gh pr view, gh pr diff, existing comments)
  • Read changed files and surrounding code in the checked-out repo
  • Trace callers/callees to confirm each suspicious hunk
  • Verify the prior finding (CLI re-wedge) is addressed by the new commit → resolved
  • Hunt for NEW concrete defects in the current diff
  • Post inline findings (concrete defects only, not previously reported)
  • Post summary

1 new defect found (1 inline comment):

severity file:line finding
minor codeframe/cli/app.py:2725 cf work resume exits 0 when execute_agent returns a FAILED state — work_start exits 1 on the same condition (app.py:2610–2611), and the PR body claims resume should "match cf work start --execute". Scripting cf work resume <id> && next proceeds on a failed execution. execute_agent does return AgentState(FAILED) (runtime.py:753/994), so this branch is reachable. Inline suggestion posted.

Follow-up on the prior round's major finding. The first push's finding (cf work resume re-wedges the run when execute_agent raises up front — missing/revoked API key) is resolved by 1a3ddbb: the new except Exceptionruntime.fail_run(...) recovery resets the run to FAILED (task stays retryable), mirroring the web worker's #722 path. The nested except: pass correctly swallows the only fail_run failure mode — an already-terminal run (runtime.py:403). Verified execute_agent always returns an AgentState (never None), so the post-call state.status read can't AttributeError.

No other concrete defects found. The web-endpoint extraction (_spawn_agent_worker) is a clean move of the existing start_single_task thread body; resume_run → RUNNING → worker runs the same run; up-front failures reset to FAILED via fail_run; double-resume is guarded by core (Run is not blocked). tasks_v2.py defines its module logger (line 40), and the CLI path correctly avoids one (no module logger in cli/app.py), so the commit's noted NameError does not occur.

Comment thread codeframe/cli/app.py
@frankbria
frankbria merged commit 8129610 into main Aug 1, 2026
12 of 13 checks passed
@frankbria
frankbria deleted the fix/1005-web-resume-worker branch August 1, 2026 09:20
frankbria added a commit that referenced this pull request Aug 1, 2026
…follow-up) (#1021)

Post-merge review finding on #1020. `cf work resume` printed "Task execution
failed" and exited 0, so `cf work resume <id> && next_step` proceeded as if
the run had succeeded. `work start` exits 1 for anything short of COMPLETED.

Matched to start exactly — BLOCKED and FAILED both exit 1. The reviewer
suggested FAILED only, on the grounds that blocking again is a normal outcome
of a resume; that is true, but it is equally true of `work start`, and one
predictable rule across both commands beats two that differ by a case.

Parametrized test pins all three outcomes.
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.

[P0.26] Web resume flips run state but never starts a worker, stranding the task

1 participant