Skip to content

fix(engine): finish the commit on resume for tasks persisted in the COMMITTING window#117

Merged
pbean merged 1 commit into
mainfrom
fix/115-committing-window-redrive
Jul 10, 2026
Merged

fix(engine): finish the commit on resume for tasks persisted in the COMMITTING window#117
pbean merged 1 commit into
mainfrom
fix/115-committing-window-redrive

Conversation

@pbean

@pbean pbean commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Problem (#115)

_commit persists phase: committing (the save right after advance(COMMITTING)) before verify.finalize_commit runs and the DONE save stamps commit_sha. A host death in that window leaves a task whose work is fully verified — dev, review, and deterministic gates all passed — sitting above baseline. On resume it matched no arm of _finish_inflight: not the spec-approval arm (wrong phase), not the #100 replay arm (there is no COMMITTING-keyed session record to replay; the _skip_review_and_commit path reaches COMMITTING having run zero review sessions). It fell through to resume-restart → _rollback_or_pause, rolling back or pausing over finished work.

Fix

Refactor: the post-gate body of _commit moves verbatim into _finalize_commit_phase(task), preconditioned on a persisted COMMITTING phase. _commit is unchanged in behavior: gate check → advance(COMMITTING) → save → helper.

New resume arm (_finish_inflight, between the replay arm and resume-restart): a task persisted at COMMITTING journals resume-commit and re-enters _finalize_commit_phase directly (with the same isolated-worktree reopen/integrate wrapper as the other arms). Design points:

  • pre_commit_gate workflows are deliberately NOT re-run. The COMMITTING save lands only after the gate loop returns clean, so the persisted phase is durable proof they passed; the tree is unchanged since (crash — no sessions ran). A re-run would double-charge the session budget through _run_session, and a blocking failure would need an illegal COMMITTING→DEFERRED move.
  • The pre_commit hook DOES re-fire — the commit message is regenerated, so a plugin rewrite reaches the squash, and a pause veto still escalates (COMMITTING→ESCALATED is legal). TEA's gate enforcement re-reads its on-disk artifacts fail-open, so re-emitting is safe.
  • finalize_commit needs no change. It is content-idempotent across both crash states — pre-squash (skill commit chain above baseline) squashes normally; post-squash (squashed commit at HEAD, clean tree) re-squashes to an identical-content commit. Now pinned by a run-twice characterization test.
  • commit_sha persisted at COMMITTING is always empty (stamped only by the DONE save) and is write-only — never routing.

Sweep parity: SweepEngine._recover_inflight_bundle had the identical hole (a COMMITTING bundle hit the rollback+PENDING reset). It gets the identical arm and closes the bundle instead of restarting it.

Tests

  • test_resume_committing_finishes_commit_to_done — the issue's environment (rollback OFF): resume → DONE, one squashed commit above baseline, commit_sha stamped, zero new sessions, no resume-restart/rollback-manual-required.
  • test_resume_committing_post_squash_still_one_commit — finalize completed just before the death: re-drive converges on exactly one commit, no stacked second squash.
  • test_resume_committing_git_error_escalates — a rejecting hook during the re-drive → ESCALATED with the attempt chain intact at HEAD.
  • test_worktree_resume_committing_finishes_and_merges — isolated flavor: finished inside the still-mounted worktree and merged back.
  • test_pre_commit_gate_workflow_not_rerun_on_commit_resume — a blocking pre_commit_gate workflow is not re-charged on the re-drive.
  • test_pre_commit_hook_fires_on_commit_resume / test_pre_commit_pause_veto_on_commit_resume_escalates — hook re-fires; rewrite reaches git; pause veto escalates legally.
  • test_finalize_commit_rerun_is_content_idempotent — run-twice pin (tree identity, single commit; no sha-inequality assertion — same-second re-runs re-mint the same sha).
  • test_resume_committing_bundle_finishes_commit — sweep flavor, production-faithful: a real run crashed on the pre_commit emit, resumed to DONE with the ledger closed.

Shared fabricator committing_crash_state in conftest persists the exact state.json shape from the issue (attempt commit above baseline — only the work file, keeping the sprint board untracked as production does; spec done; sprint synced at dev time; review_cycle=0 for skip-review fidelity).

Full suite: 2067 passed, 1 skipped. trunk check clean.

Closes #115

Summary by CodeRabbit

  • Bug Fixes
    • Improved resume handling after crashes during commit finalization.
    • Prevented completed stories and sweep bundles from being restarted or losing progress.
    • Ensured resumed commits avoid repeating completed gate workflows and produce a single clean commit.
    • Improved recovery for isolated workspaces and post-squash crash states.
    • Added safeguards for commit failures and pre-commit pauses during recovery.

…OMMITTING window

A host death after _commit persisted phase=committing but before the DONE
save stamped commit_sha matched no resume arm — there is no
COMMITTING-keyed session record to replay (_skip_review_and_commit reaches
COMMITTING having run zero review sessions) — so _finish_inflight fell
through to resume-restart and rolled back / paused over fully-verified work.

Resume now finishes the commit in place. The post-gate body of _commit
moves into _finalize_commit_phase, whose precondition is a persisted
COMMITTING phase: the new resume arm calls it directly, deliberately NOT
re-running the pre_commit_gate workflows (the persisted phase is durable
proof they passed; a re-run would double-charge the session budget, and a
blocking failure would need an illegal COMMITTING->DEFERRED move). The
pre_commit hook does re-fire — the message is regenerated, a rewrite
reaches the squash, and a pause veto still escalates (COMMITTING->ESCALATED
is legal). finalize_commit needs no change: it is content-idempotent across
both crash states (pre-squash chain and post-squash single commit), now
pinned by a run-twice test.

SweepEngine's _recover_inflight_bundle had the identical hole and gets the
identical arm; a COMMITTING bundle journals resume-commit and closes
instead of restarting.

Closes #115
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abd17771-9bef-44ec-a834-06fd881ca68b

📥 Commits

Reviewing files that changed from the base of the PR and between 658f67d and 79006aa.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • src/bmad_loop/engine.py
  • src/bmad_loop/sweep.py
  • tests/conftest.py
  • tests/test_engine.py
  • tests/test_engine_worktree.py
  • tests/test_hook_bus.py
  • tests/test_plugin_workflows.py
  • tests/test_sweep.py
  • tests/test_verify.py

Walkthrough

COMMITTING tasks now resume by completing the existing commit instead of restarting verified work. The change covers isolated worktrees, sweep bundles, hook and workflow behavior, idempotent squashing, escalation, and crash-recovery tests.

Changes

COMMITTING Resume Recovery

Layer / File(s) Summary
Idempotent commit finalization
src/bmad_loop/engine.py, tests/conftest.py, tests/test_verify.py
Commit finalization is extracted into a reusable path that regenerates the message, emits pre_commit, finalizes the commit, records its SHA, and reaches DONE across pre- and post-squash crash states.
Resume orchestration
src/bmad_loop/engine.py, src/bmad_loop/sweep.py, CHANGELOG.md
Engine and sweep recovery recognize Phase.COMMITTING, journal resume-commit, finalize in the correct workspace, and avoid the prior restart path.
Resume regression coverage
tests/test_engine.py, tests/test_engine_worktree.py, tests/test_hook_bus.py, tests/test_plugin_workflows.py, tests/test_sweep.py
Tests cover normal and isolated recovery, hook re-emission and vetoes, workflow non-reexecution, escalation, commit convergence, session reuse, and deferred-work closure.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PersistedRunState
  participant Engine
  participant Git
  participant Worktree
  PersistedRunState->>Engine: load task in COMMITTING
  Engine->>Engine: record resume-commit
  Engine->>Worktree: reopen isolated workspace
  Engine->>Git: emit pre_commit and finalize commit
  Git-->>Engine: return commit SHA
  Engine->>PersistedRunState: save DONE and commit_sha
  Engine->>Worktree: merge unit into main checkout
Loading

Possibly related PRs

Poem

I’m a rabbit with commits in a row,
No verified carrots lost below.
Resume hops through COMMITTING bright,
Squashes once and sets things right.
Hooks sing again; workflows rest—
Clean worktrees pass the test!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: committing now completes on resume for tasks stuck in COMMITTING.
Linked Issues check ✅ Passed The changes match #115 by resuming COMMITTING tasks in place, rerunning pre_commit, preserving verified work, and covering bundle recovery.
Out of Scope Changes check ✅ Passed The changelog, engine, sweep, and test updates all support the COMMITTING resume fix and do not introduce unrelated scope.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/115-committing-window-redrive

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.

@pbean

pbean commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pbean pbean merged commit 92d31a1 into main Jul 10, 2026
8 checks passed
@pbean pbean deleted the fix/115-committing-window-redrive branch July 10, 2026 20:33
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.

resume-restart discards verified work when the host dies in the COMMITTING window (no session record to replay)

1 participant