Skip to content

fix: preserve background control lifecycle status - #138

Open
donovan-yohan wants to merge 2 commits into
mainfrom
fix/t_9894076e-background-stopped
Open

fix: preserve background control lifecycle status#138
donovan-yohan wants to merge 2 commits into
mainfrom
fix/t_9894076e-background-stopped

Conversation

@donovan-yohan

Copy link
Copy Markdown
Owner

Summary

  • Preserve ScriptRunResult.stopped as background stopped instead of collapsing it to failed.
  • Add explicit background paused lifecycle support so current-run workflow_control pause denials match script-store status.
  • Add regression coverage for direct finish mapping plus background manager current-run stop/pause control-store paths.

Test plan

  • PYTHONPATH=.:src uv run --extra dev pytest -q tests/test_background.py tests/test_controls.py tests/test_run.py tests/test_capabilities.py
  • PYTHONPATH=.:src uv run --extra dev pytest -q
  • PYTHONPATH=.:src uv run --extra dev python -m compileall -q __init__.py src/hermes_workflows tests
  • uv build (passes with pre-existing setuptools license deprecation warnings)

Fixes the post-merge PR #82 background lifecycle blocker from t_9894076e.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@donovan-yohan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4e5b5d77-6a31-42f6-bf0f-f913d285ba92

📥 Commits

Reviewing files that changed from the base of the PR and between a7255d8 and f074183.

📒 Files selected for processing (2)
  • src/hermes_workflows/background.py
  • tests/test_background.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/t_9894076e-background-stopped

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "paused" state to the background workflow run states and updates the run store and manager to handle it alongside the existing "stopped" and "suspended" states. Corresponding unit tests have also been added. The feedback suggests adding "paused" to BACKGROUND_TERMINAL_STATES to prevent late operator stop requests from overwriting the paused status, and adding a regression test to verify this behavior.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/hermes_workflows/background.py Outdated

BACKGROUND_RUN_STATES = frozenset({"queued", "running", "succeeded", "failed", "stopped", "suspended"})
BACKGROUND_RUN_STATES = frozenset({"queued", "running", "succeeded", "failed", "stopped", "paused", "suspended"})
BACKGROUND_TERMINAL_STATES = frozenset({"succeeded", "failed", "stopped", "suspended"})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The new paused state is a terminal state for the process-local worker thread (similar to suspended), meaning the worker thread has exited and the background snapshot is now authoritative terminal history for that execution.

If paused is not included in BACKGROUND_TERMINAL_STATES, a late operator stop request (via store.stop) will overwrite the paused status to stopped and erase the original pause error payload. To maintain consistency with how suspended is handled, paused should be added to BACKGROUND_TERMINAL_STATES.

Suggested change
BACKGROUND_TERMINAL_STATES = frozenset({"succeeded", "failed", "stopped", "suspended"})
BACKGROUND_TERMINAL_STATES = frozenset({"succeeded", "failed", "stopped", "paused", "suspended"})

Comment thread tests/test_background.py

assert finished.status == "paused"
assert finished.result is None
assert finished.error == paused_error

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure that a late operator stop on a paused run correctly preserves the paused status and its error payload (similar to how suspended runs are tested in test_background_stop_after_suspended_preserves_terminal_error_and_status), we should add a regression test for this scenario.

        assert finished.error == paused_error


def test_background_stop_after_paused_preserves_terminal_error_and_status():
    with tempfile.TemporaryDirectory() as tmp:
        store = BackgroundRunStore(Path(tmp) / "background-runs")
        store.begin("wfs_stop_after_paused", script=SCRIPT, args={"value": "hold"})
        paused_error = {"type": "WorkflowPaused", "code": "run_paused", "message": "operator pause"}
        store.finish("wfs_stop_after_paused", ScriptRunResult(ok=False, error=paused_error, paused=True))

        stopped = store.stop("wfs_stop_after_paused", reason="late operator stop")

        assert stopped.status == "paused"
        assert stopped.result is None
        assert stopped.error == paused_error
        assert stopped.stopped_reason is None

@donovan-yohan

Copy link
Copy Markdown
Owner Author

Verdict: Approve at exact head 9fd2f0c.

No blockers found.

Coverage:

  • Reviewed src/hermes_workflows/background.py: background lifecycle state set, terminal-state protection, finish/stop race behavior.
  • Reviewed tests/test_background.py: direct finish regressions and real BackgroundWorkflowRunManager + control_store current-run stop/pause paths.
  • Cross-checked src/hermes_workflows/vm.py, src/hermes_workflows/script_store.py, src/hermes_workflows/primitives.py, init.py workflow_control status/overview plumbing, and src/hermes_workflows/controls.py for lifecycle/status contracts.

Evidence:

  • BackgroundRunStore.finish now maps ScriptRunResult.stopped before paused/suspended/ok, matching vm._final_run_status and ScriptRunStore.finish status semantics.
  • paused is accepted in BACKGROUND_RUN_STATES and error preservation covers paused snapshots; leaving paused out of BACKGROUND_TERMINAL_STATES is intentional for this gate, because a later explicit operator stop is allowed to overwrite a paused background snapshot while succeeded/failed/stopped/suspended remain protected.
  • workflow_control status/overview consume BackgroundRunRecord.status directly and should surface stopped/paused consistently with the background snapshot.
  • /simplify-style local pass: small targeted Tier-1 remediation; no reuse, quality, or efficiency cleanup worth applying. No duplicated lifecycle mapper introduced beyond the existing local store boundary.

Bot feedback + learning:

  • Bot surfaces checked: review summaries, inline PR comments, issue comments.
  • CodeRabbit: rate-limit / quota boilerplate only; noise.
  • Gemini inline comments: false positive for this gate. It asks to add paused to BACKGROUND_TERMINAL_STATES and test late stop-after-paused preservation, but the task explicitly requires paused to remain non-terminal so an explicit later stop can mark it stopped. Cute try, robot. no.
  • Substantive bot findings: 0 blocker / 0 follow-up / 1 false positive / 1 noise.
  • Learning retained: no — no new durable review class.

Verification run locally:

  • PYTHONPATH=.:src uv run --extra dev pytest -q tests/test_background.py tests/test_controls.py tests/test_run.py tests/test_capabilities.py -> pass.
  • PYTHONPATH=.:src uv run --extra dev pytest -q tests/test_background.py -q -> pass.
  • PYTHONPATH=.:src uv run --extra dev pytest -q -> pass.
  • PYTHONPATH=.:src uv run --extra dev python -m compileall -q init.py src/hermes_workflows tests -> pass.
  • uv build -> pass with pre-existing setuptools license deprecation warnings.

@donovan-yohan

Copy link
Copy Markdown
Owner Author

Formal GitHub approval was rejected because the authenticated account owns the PR, so recording the review gate as a PR comment instead.

Verdict: Approve at exact head f074183.

No blockers found.

Coverage:

  • Reviewed src/hermes_workflows/background.py: state set expansion, terminal-state preservation, stop/finish race behavior, and status mapping from ScriptRunResult.
  • Reviewed tests/test_background.py: late stop-after-paused regression, stopped/paused finish mapping, and current-run control stop/pause coverage through BackgroundWorkflowRunManager + ScriptRunStore.
  • Cross-checked src/hermes_workflows/vm.py _final_run_status and control result construction, plus existing controls/test coverage for pause/stop semantics.

Evidence:

  • BACKGROUND_TERMINAL_STATES now includes paused, so BackgroundRunStore.stop returns an already-finished paused snapshot without erasing result/error/stopped_reason.
  • BackgroundRunStore.finish now maps stopped before paused before suspended before ok, matching vm._final_run_status and preserving control-stop/control-pause outcomes instead of collapsing them to suspended/failed.
  • The added regression at tests/test_background.py:186 exercises the Gemini/QA S4 case directly: status remains paused, result remains None, pause error payload remains intact, stopped_reason remains None.
  • Current-run explicit stop/pause remains covered at tests/test_background.py:233 and tests/test_background.py:260, including persisted ScriptRunStore status and zero child-agent calls.
  • Queued/running explicit stop behavior is not weakened: queued/running records are still not terminal when store.stop transitions them to stopped; worker completion still cannot resurrect stopped records.
  • /simplify-style review: this is a two-file targeted lifecycle remediation. No duplicated lifecycle helper or broader refactor is worth forcing; adding paused to the existing local state sets is the minimal fix.

Verification run locally:

  • PYTHONPATH=.:src uv run --extra dev pytest -q tests/test_background.py tests/test_controls.py tests/test_run.py tests/test_capabilities.py -> pass.
  • PYTHONPATH=.:src uv run --extra dev pytest -q tests/test_vm_subprocess.py::test_parallel_failure_reports_parent_work_still_running_after_deadline tests/test_vm_subprocess.py::test_runaway_rpc_is_hard_capped -> pass.
  • PYTHONPATH=.:src uv run --extra dev pytest -q tests/test_background.py tests/test_script_catalog.py -> pass.
  • PYTHONPATH=.:src uv run --extra dev python -m compileall -q init.py src/hermes_workflows tests -> pass.
  • uv build -> pass, with pre-existing setuptools license deprecation warnings.
  • Full PYTHONPATH=.:src uv run --extra dev pytest -q -> failed once in tests/test_script_catalog.py::test_workflow_facade_accepts_claude_style_script_name_path_args_and_resume. That file and related implementation paths have zero PR diff, the test passes isolated, and it passes when run with tests/test_background.py; I classify this as unrelated/order-dependent suite noise, not a PR fix: preserve background control lifecycle status #138 blocker.

Bot feedback + learning:

  • Bot surfaces checked: review summaries, inline PR comments, issue comments.
  • CodeRabbit: quota/rate-limit boilerplate only; noise.
  • Gemini: substantive paused-terminal and regression-test findings are now already fixed at this head.
  • Substantive bot findings: 0 blocker / 0 follow-up / 2 already fixed / 1 noise.
  • Learning retained: no — no new durable review class beyond the existing paused/stopped lifecycle mapping checklist.

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.

1 participant