fix(ci): exempt draft PRs from OpenCode verdict polling - #1568
Conversation
Reproduced against current main (5686de4) after PR #1443 was closed as superseded by #1546's receipt-gate redesign: the redesign's PR_DRAFT plumbing only narrows which reviews opencode_review_receipt_gate.py's evaluate_receipts() accepts (rejecting a bot APPROVE on a draft) -- it never exempts a draft PR from needing a receipt at all. pr_review_merge_scheduler.py skips dispatching a review for an ordinary draft with no @opencode-agent mention, so nothing ever posts a verdict, and the "Fail closed without a current-head OpenCode verdict" step's while/sleep poll had no draft check at all -- it loops until the job's own ~360-minute runtime ceiling kills it. Add the same PR_DRAFT sourcing the sibling dispatch step already uses and an early exit mirroring the existing closed-PR exit. Minimal and scoped to the one missing exemption; the receipt-gate/scheduler architecture is otherwise untouched, per the #1443 closure's own guidance to fix this fresh against current main rather than revive that branch.
|
@opencode-agent review exact unchanged head |
|
Warning Review limit reachedNext included review available in 52 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (8)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
|
@cwl-noema-review independently review exact head |
|
@opencode-agent fix the unresolved exact-head Devin finding on the existing branch, then re-review the new head. Add |
|
The current Devin finding is valid and blocks merge-readiness. Update the existing branch so |
|
Keep non-merge-ready until the open Devin bug is fixed on this branch. Add |
Devin Review on #1568 found that pull_request_target.types never listed converted_to_draft, so a PR converted to draft while an earlier event's "Fail closed without a current-head OpenCode verdict" poll was still in flight never fired a fresh run to cancel it via the workflow's PR-scoped cancel-in-progress:true concurrency group -- the stale non-draft poll kept calling the Reviews API toward the job's runtime ceiling for a verdict a draft PR can never receive. converted_to_draft is now in the trigger list. The existing PR_DRAFT exemption in that step already exits before Reviews API access; the gap was purely that the trigger never fired for this event, so no step-body logic changed. Also ports the known SIGPIPE flake fix (cat >/dev/null in the fake gh's dispatches branch) into this branch's copy of test_scheduler_wake_reuses_trusted_receipt_predicate, inherited via merge from main and confirmed clean over 75 repeated runs. Full suite: pytest 2251 passed / 1 skipped / 21 subtests (99% coverage, pre-existing gap owned by #1567); test_strix_quick_gate.sh full harness: PASS.
|
Pushed Root cause: the Fix: added Regressions added:
Both existing literal trigger-type contract pins ( Also ported the known SIGPIPE flake fix ( Validation: full pytest 2251 passed / 1 skipped / 21 subtests (99% coverage, pre-existing gap owned by Ready for fresh exact-head review. Generated by Claude Code |
Devin Review found a second gap on #1568: converted_to_draft now fires this workflow (previous commit), but the sibling "Request current-head OpenCode review execution" step -- which runs before "Fail closed" -- had no draft exemption of its own. It still fetched the receipt-gate helper source and queried the Reviews API for a PR that just went draft, and could reach OIDC token exchange and a repository_dispatch scheduler wake before "Fail closed"'s exemption ever ran. Add the same PR_DRAFT early exit, before any API call, mirroring the existing "Fail closed" step's precedent. ready_for_review and the explicit draft-review dispatch path in pr_review_merge_scheduler.py are untouched. New regressions: test_request_review_step_exempts_a_pr_converted_to_draft_before_any_api_call proves the step exits before any gh invocation when PR_DRAFT=true; test_request_review_step_still_dispatches_for_a_non_draft_pr proves non-draft PRs are unaffected. Full suite: pytest 2253 passed / 1 skipped / 21 subtests (99% coverage, pre-existing gap owned by #1567).
|
Pushed Fix: added the same Regressions:
Validation: full pytest 2253 passed / 1 skipped / 21 subtests (99% coverage, pre-existing gap owned by Ready for fresh exact-head review. Generated by Claude Code |
Reconcile #1568 non-destructively with protected main@b4f7b082536d2be8dceab0a40a484161b50e5acd. Preserve the fixed converted-to-draft trigger and pre-I/O draft exemptions, retain newer protected-main documentation, and carry the concurrent review-event scheduler wake regression in a dedicated test file. No force push, destructive rebase, gate weakening, or predecessor evidence transfer.
Converge #1568 non-destructively with protected main@36ff97a8c25cc47a3d7c817bfc8fb6ba3108f2b7 after the scheduler-coverage chicken-and-egg repair landed. Preserve the live draft/head revalidation and its regressions while retaining current-main queue-contract authority and dropping the temporary duplicate review-event test relocation. No force push, destructive rebase, gate weakening, or predecessor evidence transfer.
|
/oc Fix the two still-valid unresolved substantive findings on this same branch/head lineage, then rerun the focused exact-trigger/verdict regressions before resolving anything:
Preserve explicit mention-triggered draft review behavior, PR-scoped concurrency, fail-closed API errors, and all existing review/credential/merge gates. Do not weaken verdict polling or synthesize status evidence. Add deterministic regressions for both event-order inversions and update the same PR branch non-destructively. |
Apply the queued one-shot repair workflow's already-designed patch
directly instead of waiting on it: the Actions fleet has 800+ runs
queued right now, and Devin flagged that workflow's contents:write
permission on branch-controlled code as a standing exposure for as
long as it sits unexecuted. Applying the identical transformation
here and deleting the workflow in the same commit closes that
window immediately rather than leaving it queued indefinitely.
- _write_refusing_gh -> _write_live_pr_then_refusing_gh: serve the
one authoritative live PR lookup the production step now performs
before continuing to refuse every other gh call.
- Thread LIVE_PR_JSON through _run_fail_closed_step,
_run_request_review_step, and test_scheduler_wake_reuses_trusted_receipt_predicate's
bespoke fake gh so each fixture answers that lookup consistently
with its own draft/head scenario.
- Update message assertions to the current production wording
("PR is still a draft on the live exact head", "unexpected gh
invocation after live-state validation").
- Remove the now-executed source-fix-1568-live-state-regressions.yml.
Verified: PYTHONPATH=. python -m pytest tests/test_opencode_required_verdict_regression.py tests/test_opencode_live_draft_state_regression.py tests/test_required_workflow_queue_contract.py -q -> 108 passed;
full PYTHONPATH=. python -m pytest tests -q -> 2281 passed, 1 skipped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
…tate lookup Devin Review flagged that this record still promised an exit before any API call, but both the request-review and required-verdict polling steps now make one unconditional gh api live-PR lookup before exiting on a confirmed live draft state (added after the initial fix so a stale event-payload PR_DRAFT/head can't be trusted alone). Update the repair description to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
CI-red root cause and fix (exact-head-path-policy)
Root causes, all now fixed:
For (4), I found Also fixed Verified locally: Pushed as Generated by Claude Code |
|
@opencode-agent Fix the two new unresolved exact-head defects on the existing branch immediately, test-first and without force-push. 1) Do not let an old-head delayed |
…closed state Owner-directed fix for two Devin Review findings on #1568's live-state validation (head/draft lookup landed in an earlier commit on this branch): 1. Stale runs could cancel the current check. The concurrency group was keyed only by repository and PR number; GitHub cancels whichever run is currently active in a group when a new one starts, with no notion of "older"/"newer", so a delayed run for an older head could cancel the newer, authoritative head's still-valid run before that older run's own live-head check ever had a chance to reject it. Scope the group by exact head SHA too, so different heads no longer share a cancellation domain while same-head events (converted_to_draft/ready_for_review, a synchronize retry) still do -- preserving converted_to_draft's ability to retire an active same-head verdict poll. 2. A delayed non-closed event ignored a live-closed PR: live_pr only ever extracted head and draft. Both admission blocks now also extract and validate live state, exiting before any further API call when it is "closed" -- mirroring the existing PR_ACTION == "closed" event-level short-circuit but driven by live truth. A missing, null, non-string, or otherwise unrecognized state value fails closed rather than assuming open, matching the existing live_head/live_draft validation style. New regressions: a structural contract test for the head-scoped concurrency group; step-body coverage for a stale non-closed event against a live-closed PR (both admission steps), live-closed state taking precedence over a stale live-draft flag, and each invalid state shape (missing/null/non-string/unexpected value) failing closed. Updated every existing LIVE_PR_JSON test fixture to include a state field now that production requires one. Verified: PYTHONPATH=. python -m pytest tests -> 2294 passed, 1 skipped, 21 subtests; coverage on scripts/ci -> 100%; interrogate -> 100%. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
|
Both new exact-head defects fixed on this branch, test-first, no force-push. 1) Stale-run cancellation. The concurrency group ( 2) Live closed-state validation. Preserved: the already-fixed effective-live-draft receipt logic, and current-main semantics elsewhere. After GREEN: full suite Pushed as @opencode-agent please re-fetch fresh review/check evidence at this head. Generated by Claude Code |
…ronize Devin Review round 3 on #1568: scoping the concurrency group by exact head SHA fixed the wrong-direction cancellation (a delayed old-head run could no longer cancel a newer, authoritative run) but also disabled the legitimate one -- a genuine new commit no longer shares a group with its own PR's now-obsolete previous-head poll, so nothing cancels it. That older run's live-head check ran once, before entering the unbounded Reviews API wait loop, which never re-validates the head on later iterations; left alone it occupies a hosted runner until GitHub's own per-job ceiling. Add a cancel-superseded-opencode-review-runs job, scoped to synchronize events, mirroring the already-established live-head-validated cleanup pattern in strix.yml's own cancel-superseded-pr-runs job: list this PR's other active Required OpenCode Review runs (matched by workflow name/event plus a display-title or pull_requests[] PR-number match), exclude the currently-executing run and any run already on the live head, and cancel the rest -- re-verifying the live head immediately before both the listing pass and each individual cancellation, so a delayed/stale invocation of this same job cannot itself wrongly cancel a still-authoritative run. New regressions: the embedded run-selection jq filter extracted and executed against synthetic workflow_runs payloads (mirroring how runtime_verdict() already exercises the required-verdict filter) -- superseded-run selection, current-head/self-run/other-PR/other-workflow exclusion, and pull_requests[] metadata matching when display_title never rendered the head suffix; a structural test pins the job's synchronize-only trigger and actions: write permission. Verified: PYTHONPATH=. python -m pytest tests -> 2301 passed, 1 skipped, 21 subtests; coverage on scripts/ci -> 100%; interrogate -> 100%; YAML parses cleanly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
|
Fixed the third round's 🔴 finding: head-scoping the concurrency group fixed the wrong-direction cancellation but disabled the legitimate one. A genuine new commit ( Fix: added Regressions: the embedded run-selection Full suite: 2301 passed, 1 skipped, 21 subtests; coverage and docstrings both 100%. Pushed as @opencode-agent please re-fetch fresh review/check evidence at this head. Generated by Claude Code |
…n cleanup job Devin Review on #1568: the cleanup job added in the previous commit had a misplaced trailing line. The job append edit was anchored on the "Fail closed" step's own closing if/fi block, but that step's script actually had one more line after it -- echo "Current-head OpenCode verdict: ${verdict}." -- ending the file without a trailing newline, so wc -l undercounted it and a manual tail read (limit=2) stopped one line short. The new job's content landed between the fi and that trailing echo, pulling it into the cleanup job's own script, where $verdict is never set. Under set -euo pipefail (-u included), every synchronize event crashed with "verdict: unbound variable", failing the required workflow on every new commit -- reproduced directly by extracting and executing the job's script body against fake gh/jq stubs before this fix, and confirmed GREEN after. Restored the echo to its correct original position at the end of the "Fail closed" step, and gave the cleanup job its own closing message. Also adopted force-cancel as a fallback for a run that resists normal cancellation, matching strix.yml's own cancel-superseded-pr-runs job (Devin's accompanying informational finding). Verified: PYTHONPATH=. python -m pytest tests -> 2301 passed, 1 skipped, 21 subtests; YAML parses cleanly; the cleanup job's script body executed directly against fake gh/jq stubs -> exit 0, no unbound-variable error (reproduced the original crash against the pre-fix script first). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BV96rXhqoR3tYZ9AeAVur4
|
Both caught and fixed — and this one was my own mistake, not a design gap. 🔴 Root cause: my previous edit anchored on the "Fail closed" step's closing Reproduced directly: extracted the cleanup job's script body and ran it against fake 🔍 Force-cancel fallback: adopted, matching Full suite: 2301 passed, 1 skipped, 21 subtests; YAML parses cleanly. Pushed as @opencode-agent please re-fetch fresh review/check evidence at this head. Generated by Claude Code |
…h already works Read strix.yml/opencode-review.yml/noema-review.yml directly rather than trusting the backlog's dead evidence link (naruon run 33581213829, 404, past retention). All three already cancel a PR's superseded-head review run on push: Noema via native concurrency cancel-in-progress; Strix and OpenCode Review via a dedicated, live-head-reverifying explicit cancellation job instead, specifically because naive same-group native cancellation caused a real incident for OpenCode Review (Devin Review on #1568) and would silently discard Strix's scan log. Backed by existing test coverage (test_opencode_poll_self_retirement.py and others). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nder load Verified root cause (peer session's mechanism finding, confirmed here against real data on .github#1741: 4/4 "Required OpenCode Review" SHA-scoped checks stuck queued, 0 completed): the coalescer's own PR-scoped concurrency group used cancel-in-progress:true, so under near-zero Actions admission a rapid second push cancels the coalescer's prior in-flight instance before it ever gets a runner -- it never executes for that PR, so the stale exact-head-SHA runs it exists to retire (opencode-review.yml deliberately scopes by head SHA per Devin's #1568 fix, so cross-run eviction never happens for those) pile up indefinitely instead. Read scripts/ci/current_head_run_coalescer.py's own module docstring before changing this: it re-fetches live PR/association/sibling state immediately before cancelling anything and never trusts the SHA it was triggered with, so a queued instance surviving a superseding push and running later is exactly as correct as a fresh one -- letting it run, rather than cancelling it, has no safety cost. This is the same blast-radius reasoning already used for pr-review-fix-scheduler.yml/ pr-auto-rebase.yml's existing cancel-in-progress:false choices. User-directed: "쓸데 없이 Trigger 되는 workflow가 있는 거 같은데요... 고쳐요" -- this is the concrete answer to that question for this one mechanism; a sibling session is separately investigating pr-review-merge-scheduler.yml's broader re-fire-on-every-check- completion pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Peer session's independently-verified, adversarially-checked audit (docs/doctoring/item13-stale-head-cancellation-audit-20260903.md, .github#1760): noema-review.yml's concurrency group was noema-review-<repo>-<pr> -- PR number only, no head SHA. GitHub evaluates the concurrency: block at run-creation time, before any job/step runs, using only the triggering event's own payload; it has no notion of "older"/"newer" head, it just cancels whatever is currently active in the group. GitHub does not guarantee webhook/ dispatch delivery order, so a delayed synchronize event for an OLDER head, arriving after a NEWER head's review is already running, shares that same group and cancels the newer, valid, in-flight review immediately -- before the older run ever reaches its own "reject a stale trigger" step. That step then correctly self-aborts, but only after the one valid review has already been destroyed, leaving the actual current head with none. This is the identical hazard opencode-review.yml already fixed for Devin Review's `#1568` finding. Ported the same pattern: added the event's head SHA (github.event.pull_request.head.sha for pull_request_target, github.event.client_payload.pr_head_sha for the repository_dispatch retry path -- both fields already used elsewhere in this same file's run-name:) as a third group-key segment. Different heads of the same PR can no longer share a group, so a stale event can never cancel the current head's run; same-head events (a duplicate delivery) still share one group, so the existing cancel-in-progress condition (synchronize/closed only, unchanged) still evicts a genuine duplicate. cancel-in-progress's own conditional logic was deliberately left untouched -- that policy (protecting an in-flight repository_dispatch write from cancellation) is orthogonal to this fix. New assertions in test_required_workflow_queue_contract.py confirm the SHA is present in the group formula for both trigger paths; confirmed they fail against the pre-fix group before committing. Full suite (2668 tests) passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lose #1568 instead (#1781) Explicit user directive (2026-09-03): head-SHA-scoped concurrency groups (added for Devin Review's #1568 finding -- a delayed, out-of-order run for an older head could cancel the authoritative run already active for a newer head) mean every push to a PR gets its own group, so rapid successive pushes no longer cancel each other's in-flight runs -- they queue up independently instead. That directly worsens the self-inflicted queue-thrashing pattern this org measured directly today (236/300 cancelled runs attributed to concurrent push volume). Refined during cross-session review (host 1's finding, independently verified before adopting): the real fix isn't to re-key the group but to stop cancelling within it. cancel-in-progress: true is what actually causes the #1568 wrongful kill, independent of whether SHA is in the group key -- scope by repo+PR-number only, but flip cancel-in-progress to false. With false, nothing in the group is ever preempted regardless of arrival order, so the #1568 race is structurally impossible here, not just less likely. A now-queued older-head run still gets a turn once the active run finishes, but the poll step's own live-head/live-state revalidation (already run every iteration, needed for correctness regardless of this setting) makes it self-exit within one poll_interval_seconds instead of running to completion or publishing stale evidence. Plain repo+PR-number scoping also means rapid pushes naturally serialize through one queue instead of spawning N independent per-head groups, which is what actually bounds queue depth here. Updated the workflow's own concurrency comment and three test assertions (two in test_opencode_required_verdict_regression.py, one in test_required_workflow_queue_contract.py) that pinned the old head-SHA + cancel-in-progress:true shape. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Supersedes 31e46db. That fix added the head SHA to the concurrency group to stop a delayed old-head event from cancelling a valid newer-head run (#1568-class hazard) -- correct, but costly: a SHA-scoped group gives every push its own group, so under this org's saturated Actions ceiling a stale per-push run is only retired by a separate cleanup job that shares that same congested admission queue and can itself sit stuck behind it, letting superseded runs pile up. The user pointed at the actual fix directly: the hazard is caused by cancel-in-progress:true killing whichever run is active with no older/newer notion, not by the group lacking SHA. Setting cancel-in-progress:false closes it completely regardless of group scoping -- the active run is never preempted -- while GitHub's unconditional single-pending-slot rule still evicts an intermediate queued push for free. The pre-existing "Reject a stale trigger" step already guarantees whichever instance runs never reports against the wrong commit either way. Independently corroborated same-day: strix.yml's own concurrency fix (64e67ef, #1779) already used this exact shape; a peer session applied the identical pattern to opencode-review.yml after re-deriving the same reasoning independently. Full suite (2704 tests) passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
#1786) Devin Review caught a real deadlock in #1781's redesign, independently confirmed by two peer sessions before I acted on it: the workflow-level concurrency: block (line 23, before permissions:/jobs:) applied to the ENTIRE run as a unit -- every job in the file, including the structurally-separate cancel-superseded-opencode-review-runs job. With cancel-in-progress: false, a new push's ENTIRE run -- cleanup job included -- could not even start until the group freed up, which only happens when the older run's own opencode-review-target job finishes. Since OpenCode/Noema inference deliberately has no wall-clock deadline, a long-running older-head review could then block the newer head's review indefinitely -- the opposite of what #1781 was supposed to fix. Fixed by moving concurrency: from workflow-level into job-level, scoped only to opencode-review-target (the job that actually runs the long dispatch+poll). This leaves cancel-superseded-opencode-review-runs and the lightweight bootstrap/coverage jobs completely unblocked: they start immediately on every push, and the cleanup job's own direct Actions API cancellation is what frees up the job-level slot for the new push's poll -- no deadlock, and the #1568 stale-cancels-fresh race stays structurally closed at the same time. Matches strix.yml's existing job-scoped-only reference pattern (confirmed to never have had workflow-level concurrency). host 1 applied the equivalent fix to noema-review.yml on #1661 (extracting its cleanup into a genuinely separate job) after finding this same class of bug there first. Updated two test files' assertions to match the new job-level placement, plus added explicit regression guards (no top-level `^concurrency:`, a job-level `^ concurrency:` exists) so this can't silently regress back to workflow-level scoping. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ign (#1785) Flagged by host 1 (relayed via peer 1): cancel-superseded-opencode-review-runs' own comment still described "exact-head concurrency" protecting the newer run -- that mechanism was removed in #1781 (bootstrap group is now cancel-in-progress: false, scoped by repo+PR-number only). While fixing it, found this job's actual role is more significant than its old "defense in depth" framing suggested: it's a precise, live-head- reverified, API-based sweep that already implements "cancel only outdated runs of the same PR" (re-checks the live head immediately before selecting cancellation candidates AND immediately before every individual cancel call), so it's immune to #1568's order-blind-preemption bug by construction. Now that the bootstrap group no longer auto-cancels anything natively, this job is the primary active-cancellation path, not a backup. Rewrote the comment to say so. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The update-branch merge of this PR against main silently produced TWO concurrency: keys in scorecard-analysis.yml: this PR's own SHA-scoped, cancel-in-progress:true group (added to close a #1568-class stale-cancels- fresh race), and .github#1768's independently-added, already-merged ref-scoped, cancel-in-progress:false group (added to bound unbounded concurrent Scorecard scans across a push burst). Neither git nor GitHub's merge flagged this as a conflict since the two additions don't textually overlap -- but YAML resolves a duplicate mapping key to its LAST occurrence, so #1773's own intended design was being silently discarded at parse time regardless of author intent. The two designs are also structurally incompatible as a single concurrency: block, not just redundant: SHA-scoping gives every distinct commit its own group, meaning nothing ever queues behind anything else -- restoring the unbounded-concurrent-scans problem #1768 exists to prevent. Given this organization's standing priority of reducing GitHub Actions queue congestion (a plan-level 60-job ceiling), kept #1768's ref-scoped, cancel-false group as authoritative and removed this PR's SHA-scoped block. Updated the test, doctoring record, and CHANGELOG fragment to match, documenting the conflict and resolution rather than silently picking a side. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…low (#1773) * test(scorecard): require reusable default-branch owner contract * feat(scorecard): expose reusable default-branch analysis * docs(scorecard): record owner and thin-caller rollout contract * test(scorecard): bind concurrency to the exact source SHA * fix(scorecard): prevent stale events cancelling newer scans * docs(scorecard): record exact-revision concurrency repair * docs(scorecard): add unreleased centralization ledger * test(scorecard): honor BaseLoader empty-value semantics * test(scorecard): remove implicit YAML parser dependency * fix(scorecard): resolve duplicate concurrency key from #1768/#1773 merge The update-branch merge of this PR against main silently produced TWO concurrency: keys in scorecard-analysis.yml: this PR's own SHA-scoped, cancel-in-progress:true group (added to close a #1568-class stale-cancels- fresh race), and .github#1768's independently-added, already-merged ref-scoped, cancel-in-progress:false group (added to bound unbounded concurrent Scorecard scans across a push burst). Neither git nor GitHub's merge flagged this as a conflict since the two additions don't textually overlap -- but YAML resolves a duplicate mapping key to its LAST occurrence, so #1773's own intended design was being silently discarded at parse time regardless of author intent. The two designs are also structurally incompatible as a single concurrency: block, not just redundant: SHA-scoping gives every distinct commit its own group, meaning nothing ever queues behind anything else -- restoring the unbounded-concurrent-scans problem #1768 exists to prevent. Given this organization's standing priority of reducing GitHub Actions queue congestion (a plan-level 60-job ceiling), kept #1768's ref-scoped, cancel-false group as authoritative and removed this PR's SHA-scoped block. Updated the test, doctoring record, and CHANGELOG fragment to match, documenting the conflict and resolution rather than silently picking a side. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Outcome
Ready successor for closed draft #1560 from the identical unchanged branch/head. The connected Ready mutation remains broken by a connector GraphQL schema mismatch, so this replaces only the PR conversation and regenerates exact-head evidence.
Root cause
Protected
main@5686de41660d51a7a7f22b8840dfa6ccfe5ff3f1still has an impossible draft path: the scheduler intentionally does not dispatch ordinary draft PRs without an explicit agent request, but the requiredopencode-reviewverdict-poll step has no draft exemption and can wait until the job runtime ceiling for a verdict that cannot be produced.Fix
gh, while non-draft PRs still poll.Current-main boundary
Exact head is
901dfdf6c1f93becf24096703a6dcacf4a6876b8, directly based on protected main. The repository-wide 99% coverage regression on protected main is independently owned by #1567; this PR does not claim that unrelated gap as passing evidence. Merge only after fresh exact-head checks/reviews and the merged-tree coverage prerequisite are satisfied. No #1560/#1443 evidence transfers.