Skip to content

fix(reviewer): don't reset the escalation budget on the fleet's own fix-push - #335

Merged
ProtocolWarden merged 1 commit into
mainfrom
fix/reviewer-escalation-budget-reset
Jun 18, 2026
Merged

fix(reviewer): don't reset the escalation budget on the fleet's own fix-push#335
ProtocolWarden merged 1 commit into
mainfrom
fix/reviewer-escalation-budget-reset

Conversation

@ProtocolWarden

Copy link
Copy Markdown
Owner

The bug (surfaced by #334)

A CONCERNS PR whose concerns are unsatisfiable in-diff — e.g. a docs PR
summarizing facts that live outside the diff (CI runs, a secret refresh, sibling
PRs) — looped forever: 7 self-pushes, fix_attempts stuck at 1, accumulating
root-level evidence cruft (VERIFICATION_*.md, RESOLUTION_SUMMARY.md), and it
never escalated.

Root cause: _phase1's "head changed after concerns → reset fix state" fired on
every head move — including the fleet's own fix-push. So the escalation
budget reset each pass and never reached max_fix_attempts.

The fix

Record the head each fix pass produces (last_fix_push_sha) and reset the budget
only on an external push (current head ≠ our last fix-push). Self-pushes now
accumulate → a non-converging PR terminates (close + requeue) instead of
churning. External (human) pushes still reset for a fresh review.

This matters more now that Part B (#333) made reviewer-verdict a required
check: the loop became a hard merge blocker, not just advisory churn.

Tests

test_phase1_self_pushed_fix_does_not_reset_budget (→ fix_attempts 2,
last_fix_push_sha recorded) and test_phase1_external_push_resets_budget (→ 1).
Reviewer suite 118 pass; audit clean.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

🤖 Generated with Claude Code

…ix-push

A CONCERNS PR whose concerns are unsatisfiable in-diff (e.g. a doc summarizing
out-of-diff facts) looped forever: each auto-fix pushed a new head, which
_phase1's "head changed after concerns → reset fix state" treated as new work
and zeroed fix_attempts — so the budget never reached max_fix_attempts and the
PR never terminated (observed on #334: 7 self-pushes, fix_attempts stuck at 1,
accumulating evidence-file cruft).

Fix: record the head each fix pass produces (last_fix_push_sha) and reset the
budget ONLY on an EXTERNAL push (current head ≠ our last fix-push). Self-pushes
now accumulate toward max_fix_attempts, so a non-converging PR terminates
(close+requeue) instead of churning. External (human) pushes still reset for a
fresh review, unchanged.

Surfaced now because Part B (#333) made reviewer-verdict a required check, so
the loop became a hard merge blocker rather than advisory churn.

Tests: self-pushed fix preserves the budget (→2); external push resets (→1).
Reviewer suite 118 pass; audit clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ProtocolWarden
ProtocolWarden merged commit 9ff798c into main Jun 18, 2026
21 checks passed
@ProtocolWarden
ProtocolWarden deleted the fix/reviewer-escalation-budget-reset branch June 18, 2026 23:48
ProtocolWarden added a commit that referenced this pull request Jun 19, 2026
#337)

* fix(reviewer): apply a docs-only rubric so doc PRs aren't over-flagged

Root fix for the over-flagging that looped #334 (the escalation-budget fix #335
only bounded the symptom). When a PR's diff is documentation-only (every changed
file is .md/.markdown/.rst/.txt or under docs/), the self-review prompt now
carries a docs rubric: review for internal consistency, accuracy against the
repo, broken references, and clarity — and explicitly DO NOT raise concerns that
a statement is "unverifiable in the diff", "lacks CI/test evidence", or
"references work outside this diff". A document legitimately summarizes and points
to work it cannot contain (CI runs, secrets, sibling/other-repo PRs); demanding
in-diff proof of those is not a valid concern for a docs PR.

Mixed (doc+code) and config-only diffs (e.g. .console/reconcile.yaml) still get
the full review rubric — only all-documentation diffs are right-sized.

Helpers: _is_doc_path / _files_from_diff / _diff_is_docs_only; rubric
_DOC_ONLY_REVIEW_RUBRIC. Tests: path classification, docs-only detection, and
that _phase1 injects the rubric for docs-only diffs and omits it for code.
Reviewer suite 123 pass; audit clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(reviewer): move _pr_files assignment before if/else for all code paths

The _pr_files variable is used unconditionally at line 2112 to determine if
a diff is documentation-only. Previously it was assigned conditionally:
- In the if branch (large diffs): via gh_client.list_pr_files()
- In the else branch (small diffs): via _files_from_diff()

This fragile approach could break if there was any code path where _pr_files
wasn't assigned. Move the baseline assignment (_files_from_diff) before the
if/else block so _pr_files is always defined, then optionally override it
with the GitHub API call for large diffs.

Resolves self-review concern: ensures _pr_files is defined for all code paths.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

---------

Co-authored-by: ProtocolWarden <ProtocolWarden@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Operations Center Bot <operations-center-bot@example.com>
ProtocolWarden added a commit that referenced this pull request Jun 19, 2026
#335 stopped the fleet's own fix-push from resetting the fix_attempts budget by
matching the recorded last_fix_push_sha. But that SHA is recorded only AFTER the
fix pass returns — so if the watcher is interrupted between the push and the
record (a long fix pass killing the process, as seen on #337), the SHA is lost
and the next poll mistakes our own push for an external one, resetting the budget
and re-opening the #334 infinite-loop risk.

Add a restart-safe fallback using state that survives the pre-fix save: when
there is an active fix cycle (fix_attempts > 0) but the pass outcome was never
recorded (last_fix_pass_pushed absent — popped at dispatch start, re-set only on
completion), a head move is our interrupted fix's push, not an external one — so
do not reset. A poll never observes this mid-dispatch (the dispatch is
synchronous within one poll), so the fallback only triggers after a restart.
External pushes after a COMPLETED pass still reset for a fresh review.

Tests: restart-mid-fix preserves the budget (→2); the self/external-push tests now
set last_fix_pass_pushed=True to model a completed prior pass. Reviewer suite 124
pass; audit clean.

Co-authored-by: ProtocolWarden <ProtocolWarden@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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