fix(scheduler): skip review dispatch when the merge tree cannot materialize - #1973
Conversation
…ialize coverage-source-tree must materialize the pull request merge tree before it can measure anything, and git cannot do that while the head conflicts. The scheduler had no way to know that, so it re-dispatched conflicting heads on every tick. Measured across the 400 most recent opencode-review-dispatch runs: 55 were repeats at an already-dispatched head, and four CONFLICTING pull requests produced 35 of them. The worst, .github#1529 at c352014, took 20 dispatches across 80.5 hours and produced no review. Every attempt failed at the same step with the same log line, "Coverage merge tree could not be materialized". Nothing stopped it because admission idempotency is per-run only, its state file living in RUNNER_TEMP, and the one cross-run debounce engages only when the pull request already carries a current-head coverage change-request review. The guard sits in dispatch_opencode_review, ahead of review_dispatch_admitted, so a conflicting head no longer spends a dispatch budget that defaults to one per run. UNKNOWN mergeability is deliberately not blocked, so an uncomputed merge state cannot starve a reviewable pull request. All six callers learn the new result, because each one's fall-through reports a dispatch that did not happen. Refs #1972 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 55 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 (2)
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 |
독립 검증 (host 1, 병합자 몫)작성자와 다른 세션에서, 인용이 아니라 제 실행으로 확인했습니다. 트리는 코드에서 확인한 것가드 위치가 맞습니다. 호출자 누락 없음. 깔때기에만 가드를 넣었으면 시스템은 안전하되(디스패치가 안 나감) 호출자의 fall-through 가 인식 못 한 반환값을 "dispatched" 로 보고했을 것입니다. 텔레메트리가 거짓말을 하는 쪽이 더 나쁩니다 — 이 버그를 찾은 도구가 그 텔레메트리였으니까요. 여섯 곳 전부 메시지를 고친 판단에 동의합니다. UNKNOWN 을 안 막은 것도 맞습니다. GitHub 이 병합 커밋을 계산 중일 때 주는 값이라, 여기서 막으면 멀쩡한 PR 이 굶습니다. 음성 대조가 UNKNOWN/BEHIND/BLOCKED/CLEAN 넷 다 디스패치되는 것을 고정하고 있습니다. 새 정책이 아닙니다. 남는 사항 (결함 아님)가드가 현재 상태head 🤖 Generated with Claude Code |
교차 세션 검증 — 통과작성자와 분리된 세션이 호출자 완결성누락 0. 각 호출자의 fall-through 가 "dispatched" 를 보고하므로 한 곳만 빠져도 쏘지 않은 리뷰를 쐈다고 기록하게 되는데, 그 위험이 없습니다. UNKNOWN 비차단 — 테스트가 아니라 실제 PR 로 확인
#1964 가 이 시점에 실제로 판별력 — 독립 변이작성자 테스트를 신뢰하지 않고 별도 변이 둘을 만들어 실행했습니다. 기준선 331 passed.
변이 E 가 기아 음성 대조 하나만 실패시킵니다. 예산 비소비도 테스트가 명시 단언합니다( 문제 주장 재확인 — 더 넓은 창최근 100건 조회로는 본문의 20건/80.5시간은 이 창의 부분집합이고 방향이 보수적입니다. 한 군데는 표현을 좁히는 편이 정확합니다. 27건 중 20건이 실패 run 게이트
판정: 병합 가능. 🤖 Generated with Claude Code |
정정: 주석의
|
…ment The original 20/80.5h came from a "most recent 400 runs" window that did not reach back to the pull request creation, so it truncated silently. Recounted over a window starting before .github#1529 existed: 27 dispatches on one head across 100.8 hours, zero successes, 20 cancelled and 7 failed. The 7 that reached coverage-source-tree are exactly the 7 that failed there; the 20 cancelled never started that job. The waste is larger than first claimed, so the guard is more justified, not less. Corrected because a measured value in production source gets quoted as fact, which is how the truncated number reached here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tch-for-unmaterializable-merge-tree
|
Merged as Merger verification — my own runsScope. Against current Budget preservation. The guard is at line 3691 and Negative control. Removing only the 15 guard lines fails exactly two tests: The second is the more interesting of the pair: it pins that the guard reads the REST merge state Gate: 2968 passed, 1 skipped, 21 subtests; coverage 100% (13196 statements, 0 missed); The cited measurement was wrong and was fixed before mergeThe comment read "20 dispatches across 80.5 hours". Two sessions independently recounted Worth recording as method: proving a count is complete is not the same as sampling enough of it. "I A related finding came out of the recount and is already corrected in the issue and PR bodies: of the AuthorizationThe authority is this session's standing instruction to clear the queue, which lives outside this Corroborating rather than authorizing: this PR's own diff edits Required-context state at this head, measured directly: of the 12 required contexts, 9 were never |
Ordinary non-force adoption of protected main@ad0779bee66624c3997947d7691f4b0dbb973be1. Preserve #1973 scheduler conflict-admission changes byte-for-byte while retaining all #1644 governance reconciliation semantics. Exact-tree verification: 582 focused tests; governance 185 tests; reconciler 396 statements / 154 branches at 100%; docstrings 100%; full suite 3124 passed, 1 skipped, 21 subtests. The first full invocation exposed only an unseeded local uv virtualenv missing pip; after matching hosted setup-python's pip presence, the exact failing test and full suite passed without source changes.
…ree cannot materialize (#1973)" (#1985) This reverts commit ad0779b. I wrote that guard and its justification is false. It blocked every OpenCode dispatch on a DIRTY/CONFLICTING head on the grounds that "a conflicting head can only produce a failed dispatch". The run object does conclude failure, but the review is published anyway: the reviewer reads the pull request diff, not a merge tree -- "Coverage is a separate gate", in the review's own words -- and only `coverage-source-tree` needs the merge commit. Measured on .github#1529, the PR that motivated the guard: the last of its 27 dispatches published a 2404-character review at 2026-09-05T19:40:58Z. That review body carries its own run id, 33969161561, which concluded failure at the receipt gate nine seconds later. It is the only OpenCode review that head has (4 reviews total, 1 by opencode-agent[bot]) and it is why the PR now reads as reviewed. The guard would have discarded it. The pattern is not unique to #1529: .github#1555 is CONFLICTING right now and its current head carries a 2019-character CHANGES_REQUESTED review from the same reviewer. I reached "produced nothing" by reading run conclusions, which are roll-ups that cannot name what a run did. The cost the guard claimed to protect is also wrong by two orders of magnitude. Across all 27 dispatches of that head, 109 jobs: 27 were allocated a runner for 0.24 h in total, 82 never were, and the rest of the elapsed time was queue residency. Blocking the whole chain would have saved fourteen minutes of runner time. The repeat itself is a real problem, and it is fixed at its cause rather than here. `active_review_run_refs` matched a run's `name` exactly against the review workflow aliases, but the central review workflows define `run-name:`, so GitHub sends the rendered title in that field and every dispatch run was filtered out before the check could see it -- `already_running` never fired. With that repaired, a conflicting head receives one dispatch and the next is suppressed while it runs, which is the outcome this guard was reaching for without discarding the review. Developer experience: the scheduler no longer prints a skip reason that asserts an outcome contradicted by the runs it cites. User experience: a conflicting pull request receives a review and repair guidance, instead of repair guidance alone. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Closes #1972.
The waste this removes
coverage-source-treemust materialize the pull request merge tree before it can measure anything, and git cannot do that while the head conflicts. The scheduler had no way to know that, so it kept re-dispatching conflicting heads.Across the 400 most recent
opencode-review-dispatch.ymlruns, 55 were repeats at an already-dispatched head. Cross-tabbed against current merge state:The worst case is
.github#1529at headc352014a: 19 dispatches, zero successes (7 failed, 12 cancelled). A peer's wider window found 27 over 100.8 hours, same zero-success result.Corrected after review. I first wrote that every attempt failed at the same step. That overstated it, and peer verification caught it. Of the 19, 8 reached
coverage-source-tree(7 concludedfailure, 1 was cancelled mid-job) and all 7 failures died there; the other 11 never started that job, having been cancelled while queued, so they cannot be attributed to the conflict. What the fix rests on is unchanged: every one of the 19 spent dispatch budget on a head that cannot succeed, and every run that got far enough to try hit the conflict:Why nothing stopped it
Admission idempotency is per-run only. The state file is
${RUNNER_TEMP}/review-admission/state.json, and the code says so itself atpr_review_merge_scheduler_core.py:6034: state is "shared by scheduler processes in this run". Every tick starts with an empty record set.The one cross-run debounce,
coverage_retry_wait_reason, returnsNoneimmediately unless the pull request already carries a current-head coverage change-request review. A pull request whose dispatches die before producing any review never reaches it, even though that function exists to prevent an "unbounded dispatch loop".What changed
The guard sits in
dispatch_opencode_review, ahead ofreview_dispatch_admitted, so a conflicting head no longer spends a dispatch budget that defaults to one per run. It reuses the existingeffective_merge_statehelper and the{"DIRTY", "CONFLICTING"}set this repository already uses in five other places.This adds no new policy. The merge scheduler already treats conflicting pull requests as needing repair rather than review, and the reviewer guidance already says a conflicting head "is not a code-review finding". Only the dispatch path was unaware.
UNKNOWNis deliberately not blocked. GitHub reports it while still computing a merge commit, so blocking on it would starve reviewable pull requests. There is a negative-control test for exactly that.All six callers learn the new result. Each one's fall-through reports a dispatch that did not happen, so without those the scheduler would log reviews it never sent, corrupting the same telemetry used to find this.
Verification
scripts/ci)Conflicting pull requests keep receiving repair guidance. They simply stop consuming review-dispatch budget while they cannot be reviewed.
I authored this, so verification and merge belong to other sessions.
🤖 Generated with Claude Code