Skip to content

fix(source-control): bind a review re-run after an unposted timeout - #12863

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/review-timeout-rerun-binds
Nov 3, 2026
Merged

kyle-sexton merged 2 commits into
mainfrom
fix/review-timeout-rerun-binds

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: a merge-gate defect found while measuring the pr-merge wakes for #12445; open-issue intake is closed while issues are driven to zero.

Summary

Six lanes App PRs (#12814, #12812, #12795, #12665, #12636, #12604) can't be armed. The merge gate refuses each with evidence-unbound:claude-review: not bound: rerun-not-allowed (and the same for the security lane). That refusal is 7 of the 25 pr-merge wakes since #12771. In each review run, attempt 1 failed while the reviews credential was rejected at its usage limit. For example, run 41626484307 attempt 1 failed at 11:31Z with claude-review: failure-class=timeout: the review leg reached its time limit and the warning REVIEWS_CLAUDE_CODE_OAUTH_TOKEN ... status=rejected. Attempt 2 then passed. timeout was not a class after which attempt 2 binds (babysit_review_evidence.py RERUN_CLASSES, and the no-outcome branch of rerun_binds), so the passing review never counted.

Fix

  • NO_OUTCOME_RE now also matches this lane's failure-class=timeout: line. A timed-out attempt 1 binds attempt 2 only under the proof no-outcome already uses: no completion record, and the lane posted nothing on the head between the two attempts (_unposted). Attempt 1 then left no verdict, so attempt 2 is the only review and binding it trades nothing away.
  • The gate still never starts that re-run itself (TIMEOUT is not in GATE_RERUN_CLASSES). A spent credential would fail the one allowed attempt.
  • reference/safety.md names the new class.

Decision: GitHub Support (hub ledger L108), relayed in session: "go with (a); you own it ... Keep the same unposted proof NO_OUTCOME uses: an unposted attempt 1 leaves attempt 2 as the only review, so binding it adds no risk."

Verification

  • test_babysit_review_evidence.py: 268 passed. The new TimeoutRerunTests use run 41626484307's annotation shape (the timeout line plus the status=rejected usage warning). They cover: binds for both lanes when nothing was posted; refuses with rerun-not-allowed when the lane posted its marker; and the gate does not start the re-run.
  • With the source change reverted, 3 of those tests fail (the bind test on both lanes, and the ev.TIMEOUT check).
  • Both lanes write the same line: claude-security-review: failure-class=timeout: the review leg reached its time limit (check run 125891481218, attempt 1 of run 41626483992).
  • scripts/run-ruff.sh check on both files: all checks passed.
  • Per-run call delta: 0. No new reads; the change touches only the classification of annotations already fetched.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 2 commits November 3, 2026 11:13
A review run whose first attempt failed with failure-class=timeout (every
review while the reviews credential was rejected at its usage limit, run
41626484307) left its successful second attempt unbound, so the merge gate
refused six lanes App PRs with rerun-not-allowed. A timeout now binds the
second attempt under the same unposted proof no-outcome uses; a lane that
posted on the head still refuses, and the gate never starts the re-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 3, 2026 16:29
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T16:31:14.077901Z 0235cc2 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 0235cc2ac856f7d5492924ae043e8ba6d0ff4055: read 4 of 4 changed file(s); 0 finding(s), 0 as inline comments.

Security review: PR #12863

No security issues found.

This PR changes how the merge gate reads review evidence. When an AI review's first attempt fails with failure-class=timeout, the gate now counts the second attempt as the review. Before, only no-outcome got that treatment. I read every hunk of all 4 changed files. One of them, plugins/source-control/skills/babysit-prs/scripts/babysit_review_evidence.py, is on a trusted path, so I read its whole change and the code it gates (rerun_binds, _unposted, lane_posted, _lane_job, _failures, _lane_line, RERUN_CLASSES, GATE_RERUN_CLASSES).

Merge-hold check. This loosens the gate's evidence binding, so I judged it under the merge-hold bullet:

  • Same checks as no-outcome. The timeout line goes through the existing NO_OUTCOME_RE branch of rerun_binds (L1182-L1186). It binds only when all of these hold:
    • the failure annotation is on this lane's own job (_lane_job) and names this lane (_lane_line);
    • the job left no completion record (not recorded(job));
    • the lane posted nothing on the head between the two attempts (_unposted → lane_posted). That covers marker creates and rewrites, lane comments and lane threads, and any input it can't read counts as posted.
  • No verdict is lost. A first attempt that timed out and posted nothing gave no verdict. The second attempt still has to pass as a full review of the same head on its own merits.
  • The gate doesn't start the re-run. TIMEOUT is not added to GATE_RERUN_CLASSES (L1143-L1145). The re-run still comes from the reconciler or a person.
  • No new way to fake the signal. It is read from the same lane-job failure annotations the existing classes use. A head that edits a caller workflow to forge them is still caught by lane_source, which the diff doesn't touch.

No token, secret, trusted actor, untrusted-text path or billing surface changes, so none of the three severity-bar impacts (leak, compromise, charge) applies.

Other files:

  • reference/safety.md and the .changes entry are prose that matches the code.
  • The test file adds tests only.
  • No standing instruction is deleted or softened.

Spend: none. The change only reclassifies annotations the gate already fetches.

critical-findings: 0

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

claude-review reviewed 0235cc2ac856f7d5492924ae043e8ba6d0ff4055: read 4 of 4 changed file(s); 0 finding(s), 0 as inline comments.

Code review: PR #12863, bind a review re-run after an unposted timeout

No findings. I read all 4 changed files in the diff: the changelog fragment, reference/safety.md, babysit_review_evidence.py (a trusted path, so it got extra scrutiny) and its tests.

What the change does. NO_OUTCOME_RE now also matches <lane>: failure-class=timeout: . Before a timed-out attempt 1 lets attempt 2 count, rerun_binds still requires the same three checks no-outcome uses (babysit_review_evidence.py#L1182-L1186):

  • the line must start the lane's own failure annotation (_lane_line), so one lane's line cannot vouch for another;
  • the job left no completion record (recorded);
  • the lane posted nothing on the head between the two attempts (_unposted), and when the inputs are unknown this reads as posted.

TIMEOUT is not added to RERUN_CLASSES or GATE_RERUN_CLASSES. So the merge gate still never starts this re-run itself, and a timeout cannot bypass the posted check. With that check in place, a timed-out attempt 1 that did leave a verdict cannot have it replaced.

Tests.

  • TimeoutRerunTests passes its own annotations to no_outcome(...), so the fixture contains only the timeout line and the usage warning, with no no-outcome line.
  • The bind test therefore runs the new regex branch. It covers both lanes, the posted-marker refusal (rerun-not-allowed) and the not-a-gate-rerun check.
  • The PR says 268 tests passed and that 3 fail with the source change reverted. That is the author's claim: this lane has no shell, so I did not run the tests.

Docs. safety.md and the changelog fragment match the code. A patch bump fits a fix to which evidence the gate accepts.

Optional, not counted. Two untouched comments still name only NO_OUTCOME for this path: the rerun_binds docstring (L1166-L1170) and the RERUN_CLASSES / GATE_RERUN_CLASSES comments (L1129-L1142). The regex name NO_OUTCOME_RE now also covers timeouts. Mentioning TIMEOUT in those places would keep the comments accurate.

Other checks.

  • Closing issue: none; the PR says it has no related issue.
  • Plugin manifests: none changed.
  • Prior findings: none (round 0 of a cap of 3).
  • Embedded instructions: none found in the PR context.
  • Spend: no extra usage. The change only reclassifies annotations the gate already fetches.

@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 4d5cd13 Nov 3, 2026
89 of 90 checks passed
@kyle-sexton
kyle-sexton deleted the fix/review-timeout-rerun-binds branch November 3, 2026 17:16
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