fix(source-control): review-trigger head-staleness hardening (BLOCKED-behind guard, stale-head reactions) - #452
Conversation
F7 — request_review.py's pre-POST freshness guard rejected only the literal BEHIND merge state, so a head behind its base that GitHub reports as BLOCKED (BLOCKED masks BEHIND) slipped through and spent the one-shot review request on a stale SHA. The guard now reuses compute_branch_freshness (off the _blocked_base_compare enrichment view_pr already computes), rejecting a compare-behind head so the branch-refresh flow runs first. F8 — the candidate predicate in babysit_review_trigger.py blocked candidacy whenever any reviewer reaction existed. Reactions carry no commit SHA, so a reaction left on an earlier head persisted onto later heads and permanently suppressed the new head's observation window. The check is now scoped to the current-head-associated reactions already computed nearby. Regression tests added to test_review_trigger_race.py (both defects and their guard cases). Dormant-by-default module. Bump source-control 0.9.0 -> 0.9.1 + CHANGELOG. Closes #327 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011V31qpAHP3jfs76B9d5Rfo
|
Claude finished @kyle-sexton's task in 3m 45s —— View job PR Review
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 24e8f7ac9e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
classify_review_request's F8 fix scoped the candidate predicate's reaction check to the current head, but request_review.py's posting guard (validate_current_candidate's pre-POST and post-POST checks) still gated on the raw, unscoped fetch_reaction_signals() result. A PR the F8 fix made eligible because its only reaction was stale (an earlier head) would still have every posting attempt rejected, recorded as "ambiguous", and blocked from retrying -- the staleness bug F8 fixed one layer up. Extract the scoping rule into a shared resolve_associated_reactions() helper in babysit_review_trigger.py and apply it in both validate_current_candidate's reaction checks (request_review.py). Regression tests added to test_review_trigger_race.py.
|
Claude finished @kyle-sexton's task in 2m 53s —— View job PR Review — Updated for commit b1a9315
What's new in b1a9315This commit addresses the Codex P2 finding from the prior review:
|
|
Classification of the claude[bot] review's observations (no reactions endpoint on review bodies — exempt per review-discipline §3 D4.5):
None of these are blockers per the reviewer's own summary ("Neither is a reason to hold this PR"). Separately, this follow-up commit ( |
|
To use Codex here, create an environment for this repo. |

Closes #327
Summary
Two head-staleness defects in the dormant-by-default AI review-trigger flow (both found by Codex on #322, P2). The module only activates when
babysit_review_trigger_phrase+babysit_review_bot_logins+babysit_review_gate_contextare configured, so neither affects the default babysit tiers.F7 — reject compare-behind BLOCKED heads before requesting review
request_review.py's pre-POST freshness guard rejected only the literalBEHINDmerge state. GitHub reportsmergeStateStatus=BLOCKEDfor a PR that is also behind its base (BLOCKED masks BEHIND), so the guard passed and posted the one-shot trigger on a stale head. The guard now reusescompute_branch_freshness(current)— off the_blocked_base_compareenrichmentview_pralready computes — so a compare-confirmed behind head is rejected and the branch-refresh flow runs first.F8 — ignore stale-head reactions for new review windows
The candidate predicate's unconditional
and not reactionsblocked candidacy whenever any reaction existed. Reactions carry no commit SHA, so a reviewer-bot reaction left on an earlier head persisted onto later heads; the new head never accruedmissing_observationsand the trigger never posted for the updated head. The check is now scoped toassociated_reactions(reactions associated with, or newly observed for, the current head — already computed nearby).Scope note
The live post-time guard at
request_review.py(if reaction_signals: raise) and the state-string branch atbabysit_review_trigger.pyremain conservative by design — they still refuse to post while any unproven reaction lingers. F8 restores the observation window (candidacy /missing_observationsaccrual) without weakening a fail-safe post guard. Fully unblocking end-to-end posting through a persistent stale reaction is a larger call related to #324 and is intentionally out of scope here.Verification
engine.test.shlocally: 184 unittest OK, ruff clean, guarded-wrapper checks pass.test_review_trigger_race.py: F7 (BLOCKED-masked-BEHIND rejected; BLOCKED-but-fresh still passes), F8 (stale earlier-head reaction no longer suppresses the window; current-head reaction still suppresses).0.9.0 → 0.9.1+ CHANGELOG.Related
🤖 Generated with Claude Code