fix(engine): emit defer record when the rollback pauses (#342) - #345
Conversation
|
Warning Review limit reached
Next review available in: 10 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR fixes a bug (#342) where
Confidence Score: 5/5The change is narrow and additive — a single try/except wrapping one existing call, a helper extraction, and two well-targeted tests. No existing code path is altered. The fix is minimal and its scope is tightly bounded to Files Needing Attention: No files require special attention. The core logic change in
|
| Filename | Overview |
|---|---|
| src/bmad_loop/engine.py | Adds _record_defer helper to consolidate three duplicate journal+notify+save sites, and wraps _rollback_or_pause in _defer with a narrow except RunPaused that emits the defer record before re-raising — the targeted fix for the lost-record bug. |
| tests/test_engine.py | Adds two targeted regression tests: rollback-OFF (production default) path and snapshot-failure path. Both check journal ordering, ATTENTION ordering, and the honesty of the note text; ablation targets named in docstrings. |
| CHANGELOG.md | New 'Fixed' entry for #342 inserted above the existing #343 entry; accurately describes the affected paths and the fix shape. |
| docs/FEATURES.md | Plateau-defer bullet extended with one sentence covering the #342 case (recovery-pausing defer now emits the record and points at the ACTION REQUIRED notice). |
Reviews (1): Last reviewed commit: "test(engine): cover defer-record surviva..." | Re-trigger Greptile
Closes #342.
Problem
Engine._deferadvances the task to terminalPhase.DEFERREDbefore recovering the tree. When_rollback_or_pausepauses instead of returning,pause_for_manual_recoverypersists thatterminal phase (
recovery_flow.pysaves before raisingRunPaused), so the tail of_defer— thestory-deferredjournal entry, the defer notification, the final save — is skipped permanently:on resume
_finish_inflightskips terminal tasks and_pick_nextskips anything already instate.tasks.Validation of #342 confirmed every mechanical claim and found the reachability is wider than the
issue states: besides the preserve-ref failure path and the post-#341 snapshot path, the
production-default rollback-OFF arm (
scm.rollback_on_failure=false) pauses on everyin-place defer with a dirty tree — no git failure needed. A second consumer the issue does not
name:
bmad-loop diagnosederivesdefer_countfrom the journal, so it under-counts on this path(phase-derived
summary.deferredstays correct).Fix — issue's shape 2 (catch + re-raise)
A narrow
except RunPausedaround the rollback inside_defer: emit the defer record, re-raise.The record reads accurate post-pause state — shape 1 (record before rollback) would emit a
stale/absent
preserve_ref(the rollback clears it and re-sets it only after preserving), andshape 3 (advance after rollback) would leave a paused task at
DEV_VERIFY/REVIEW_VERIFY, whichresume's
_finish_inflightre-drives rather than re-entering_defer— contradicting thedefer decision. The catch is record-then-reraise, not a swallow; the comment names the deliberate
exception to the unwind-to-the-top doctrine.
One honesty detail: on the snapshot-failure pause path
preserve_partialis latched while thereset was refused, so
_defer_recovery_note's "did not survive the rollback" claim would befalse. The except arm therefore uses a fixed pause-aware note pointing at the ACTION REQUIRED
notice (the authoritative pointer, written to the same ATTENTION file one entry earlier).
The three now-identical journal+notify+save sites (isolated arm, in-place tail, except arm) are
factored into one
_record_deferhelper.Tests
test_defer_record_survives_rollback_pause— full-run drive under the production default(rollback OFF, no monkeypatch): asserts the
story-deferredentry, journal ordering(
rollback-manual-required→story-deferred→run-paused), and the ATTENTION ordering(ACTION REQUIRED before the defer notice).
test_defer_record_on_snapshot_failure_pause_stays_honest— the Decide whether a failed worktree snapshot should block the rollback reset #340/fix(recovery): refuse the rollback reset when the snapshot fails (#340) #341 path: commits parked,snapshot fails, reset refused; asserts the record names the parked
attempt-preserve/*ref andthat the false "did not survive the rollback" wording does not appear.
except RunPausedarm deleted, both tests fail (nostory-deferredentry at all).
Accepted risk (deliberate, not guarded)
journal.append/_savein the except arm on a dying disk (the very ENOSPC scenario behind thesnapshot path) would replace
RunPausedwithOSError, crashing rather than pausing. This isconsistent with
pause_for_manual_recovery's own unguarded journal/notify/save one frame below —a disk that dies between the pause's save and this append crashes the run either way.
Full suite: 3403 passed, 24 skipped.
trunk checkclean.