Description
decide_review_session treats any non-completed review session status the same way: if review_cycle < max_review_cycles, return RETRY; else DEFER/PAUSE.
There is no branch for:
timeout / timeout_fired vs crash vs user-stop,
- “prior review cycle already left the story
status: done + verify-green — converge and refile residual follow-up instead of burning another full review,”
- “do not spend
max_followup_reviews after a pure timeout with no new review artifact.”
Result under unattended multi-story runs: a 90m session timeout on review-1 (or a heavy codex second-opinion) becomes review-retry → review-2 → review-3… until the outer cycle budget dies, even when the product tree from dev is already good enough to commit/salvage.
Steps to reproduce
- Policy:
review.enabled = true, review.trigger = "recommended" (or always), limits.session_timeout_min = 90, limits.max_review_cycles = 3, limits.max_followup_reviews = 1.
- Adapter review on a slower/heavier model (field: codex second opinion) so a single review pass can hit session timeout.
- Dev pass finishes with a green-enough tree and often already
followup_review_recommended from inline review.
- Watch journal:
review session non-completed → review-retry → next cycle → repeat until cycles exhausted or human stops the run.
Expected behavior
At least one of (policy-selectable is fine):
- Timeout exit class: after N timeouts (default 1) on review, if the last known good state is
spec status: done (and optional verify commands green), converge: commit / refile lingering follow-up to deferred-work, do not open another full review pass solely because the session timed out.
- No follow-up grant after timeout: a timeout that produced no successful review result_json should not consume or re-arm
followup_review_recommended damping in a way that schedules another independent pass with empty product delta.
- Journal a distinct event e.g.
review-timeout-salvage vs bare review-retry so external supervisors can act without guessing.
Actual behavior
From installed 0.8.1 escalation.decide_review_session:
if result.status != "completed":
reason = f"review session {result.status}"
if budget_left:
return Decision(Action.RETRY, reason)
return Decision(_exhausted_action(task), _exhaust_reason(task, reason))
Engine then journals review-retry and continues the review loop. Field story (health-matrix closeout style epic endgame): review-1 timeout → review-2 completes with follow-up flag → review-3 still scheduled while wall-clock and tokens burn — human/AI salvage had to stop the run and ship product via external PR because the engine would not exit the review stack.
Which area is this for?
Orchestrator / control loop
bmad-loop Version
0.8.1
Which coding CLI are you using?
Codex (codex)
(also observed with mixed claude dev + codex review)
Operating System
macOS
Relevant log output
# pattern (sanitized):
# review-session status=timeout (or non-completed)
# journal: review-retry reason="review session timeout"
# review_cycle increments
# eventually max_review_cycles → DEFER, or human bmad-loop stop
# worktree still holds good product code; origin may never see it without salvage
No diagnose dump attached (contains project paths). Happy to produce a sanitized bmad-loop diagnose excerpt if a maintainer wants a specific run shape.
Confirm
Related
Notes on philosophy fit
Still deterministic Python — classify session end reason + prior terminal status; no LLM in the control loop. Optional policy key e.g. review.on_timeout = "retry" | "salvage-if-done" | "defer" keeps default = today’s retry for safety.
Willing to PR a minimal change after design nod (CONTRIBUTING: large/behavior change → Discord first). Suggested minimal patch surface: escalation.decide_review_session + engine journal + one unit test matrix on SessionResult.status.
Description
decide_review_sessiontreats any non-completedreview session status the same way: ifreview_cycle < max_review_cycles, return RETRY; else DEFER/PAUSE.There is no branch for:
timeout/timeout_firedvs crash vs user-stop,status: done+ verify-green — converge and refile residual follow-up instead of burning another full review,”max_followup_reviewsafter a pure timeout with no new review artifact.”Result under unattended multi-story runs: a 90m session timeout on review-1 (or a heavy codex second-opinion) becomes review-retry → review-2 → review-3… until the outer cycle budget dies, even when the product tree from dev is already good enough to commit/salvage.
Steps to reproduce
review.enabled = true,review.trigger = "recommended"(or always),limits.session_timeout_min = 90,limits.max_review_cycles = 3,limits.max_followup_reviews = 1.followup_review_recommendedfrom inline review.review sessionnon-completed →review-retry→ next cycle → repeat until cycles exhausted or human stops the run.Expected behavior
At least one of (policy-selectable is fine):
spec status: done(and optional verify commands green), converge: commit / refile lingering follow-up to deferred-work, do not open another full review pass solely because the session timed out.followup_review_recommendeddamping in a way that schedules another independent pass with empty product delta.review-timeout-salvagevs barereview-retryso external supervisors can act without guessing.Actual behavior
From installed 0.8.1
escalation.decide_review_session:Engine then journals
review-retryand continues the review loop. Field story (health-matrix closeout style epic endgame): review-1 timeout → review-2 completes with follow-up flag → review-3 still scheduled while wall-clock and tokens burn — human/AI salvage had to stop the run and ship product via external PR because the engine would not exit the review stack.Which area is this for?
Orchestrator / control loop
bmad-loop Version
0.8.1
Which coding CLI are you using?
Codex (codex)
(also observed with mixed claude dev + codex review)
Operating System
macOS
Relevant log output
No diagnose dump attached (contains project paths). Happy to produce a sanitized
bmad-loop diagnoseexcerpt if a maintainer wants a specific run shape.Confirm
Related
## Auto Run Resultre-triggers the #149 livelock, then DEFER-drops a finished story #224 — review HALT / missing Auto Run Result livelock then DEFER-drop (adjacent: review leg failure modes that destroy finished work)Notes on philosophy fit
Still deterministic Python — classify session end reason + prior terminal status; no LLM in the control loop. Optional policy key e.g.
review.on_timeout = "retry" | "salvage-if-done" | "defer"keeps default = today’s retry for safety.Willing to PR a minimal change after design nod (CONTRIBUTING: large/behavior change → Discord first). Suggested minimal patch surface:
escalation.decide_review_session+ engine journal + one unit test matrix onSessionResult.status.