Skip to content

ci(review): coalesce superseded OpenCode review dispatches before admission - #1958

Merged
seonghobae merged 1 commit into
mainfrom
fix/dispatch-workflow-level-concurrency-20260906
Sep 6, 2026
Merged

ci(review): coalesce superseded OpenCode review dispatches before admission#1958
seonghobae merged 1 commit into
mainfrom
fix/dispatch-workflow-level-concurrency-20260906

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem

opencode-review-dispatch.yml carries a concurrency group only on its long opencode-review-target job. A job-level group is not evaluated while the whole run waits behind the organization job ceiling, so two dispatches for the same pull request each wait in the queue for hours and each is allocated a runner before the older one can be discarded. The three sibling required workflows already moved their groups to workflow level for exactly this reason — strix.yml's block says it in one line: "Workflow-level admission is required: job-level groups are never evaluated while the whole run is queued behind the organization job ceiling."

Measured on 2026-09-06 (full annotations, not step names): of the five dispatch runs that got past validate-pr-metadata, four were rejected hours later by opencode-review's privileged metadata check because the head had moved while they queued, and each had already run coverage-source-tree and coverage-evidence by then.

run pull request expected head → live head
34002473295 #1902 2d4624a3bf732f92
34010256951 #1661 6047f36fad65c731
34015973300 #1923 5c9920a9dc146b4c
34016922761 #1661 ad65c731946f400b

(The fifth, 34011098364, had an identical head and base but state=closed — the pull request merged at 05:04:12Z and its review ran at 07:17:32Z. This change does not address that case.)

Change

  • Workflow-level concurrency group keyed by the dispatched pull request: opencode-review-dispatch-${{ client_payload.target_repository || github.repository }}-${{ client_payload.pr_number || github.run_id }}, cancel-in-progress: true. This is the same shape and keying codeql-scan-dispatch.yml already uses at workflow level for the same trigger type.
  • The job-level group on opencode-review-target stays. Workflow-level and job-level coexist in the sibling workflows for different purposes, and behaviour between two runs that are both executing is unchanged — the job-level group already cancels there. What changes is that a superseded run is cancelled while queued, before it takes a runner.
  • The privileged metadata check is untouched. It rejected exactly what it should; the defect is that the rejection costs a runner slot.
  • Contract test test_privileged_review_dispatch_coalesces_superseded_runs_before_admission pins the workflow-level block, its key, cancel-in-progress: true, the absence of head-SHA scoping in the key (per the 2026-09-03 finding that head-SHA groups stop same-PR pushes from coalescing at all), and the continued presence of the job-level group.

Why not head-SHA scoping, and why cancel-in-progress: true

Head-SHA keying gives every push its own group, which is what stopped same-PR runs from cancelling each other in the first place. cancel-in-progress: true matches the current design on main for strix.yml, noema-review.yml and opencode-review.yml (pre-admission coalescing under the 60-job ceiling). The cancel-in-progress: false deadlock recorded for noema-review.yml — a workflow-level group capturing the workflow's own cleanup job — does not apply here: this workflow has no sweep job, and with true nothing waits for the group.

Does this cancel a review that is still valid?

No, and the reason is in scripts/ci/review_admission_controller.py rather than in this workflow (a peer raised the question and I read the code myself). An AdmissionRequest's identity is repository#pull_request@head_sha:component (:97), and plan_dispatches rejects a request whose identity is already recorded as idempotent (:337) unless that record is stale. A second dispatch for the same head is therefore never planned. When a new head is admitted, the prior queued records on the same stream with a different head are marked stale (:349-356). So two dispatch runs can coexist for one pull request only when the head has moved, and the run this group cancels is always the one whose subject already changed — the run the privileged check rejects anyway, four times out of five today.

Known property of payload-keyed admission

The group key is built from client_payload, and GitHub evaluates it when the run is created — before any job runs, and therefore before vars.OPENCODE_REPOSITORY_DISPATCH_ACTOR is read inside validate-pr-metadata. An actor able to send a repository_dispatch to this repository can therefore cancel a pull request's legitimate in-flight dispatch run even though their own run is about to be rejected at the identity gate. This is not new and is not a reason to block: sending that dispatch already requires a write-scoped token on this repository, so such an actor is inside the trust boundary already, and codeql-scan-dispatch.yml has carried the same property with the same keying since it was written. Recorded here so nobody has to rediscover it from a cancelled run months from now (found during this pull request's independent verification).

Evidence

Gate figures are in the first comment. Negative control: the new test fails against main's workflow and passes on this head.

Developer experience: a superseded review dispatch disappears from the queue when the next one is created, instead of surfacing hours later as a red opencode-review check whose real cause ("the head moved while I waited") is only visible in the job annotation.

User experience: none directly; the freed slots go back to the same ceiling every other required workflow is waiting on.

Acceptance, stated so that it can actually fail: after this lands, find a pull request with two OpenCode Review Dispatch runs, and check that the older one was cancelled at the moment the newer one was created, with every job showing steps == 0 and created_at == started_at. The pair is the load-bearing part. "Cancelled with no steps" alone proves nothing — it is equally true of a run cancelled by a person, by another workflow, or by any job that died waiting for a runner, so a cancellation without a newer sibling in the same group is not evidence for this change (one such false match was already observed on 2026-09-06 and rejected on exactly that ground).

Related: #1931 (queue delay invalidating its own dispatches — this addresses the cost of the invalidation, not the validation semantics, which are correct), #1929 (the dispatcher allowlist, which is upstream of all of this).

🤖 Generated with Claude Code

…ission

opencode-review-dispatch.yml carried its concurrency group only on the long
opencode-review-target job. A job-level group is never evaluated while the
whole run waits behind the organization job ceiling, so two dispatches for one
pull request each queued for hours and each was allocated a runner before the
older one could be discarded.

Measured on 2026-09-06: of the five dispatch runs that passed
validate-pr-metadata, four were then rejected by the privileged metadata check
because the head had moved while they queued (34002473295, 34010256951,
34015973300, 34016922761), every one of them after coverage-source-tree and
coverage-evidence had already run. The privileged check behaved correctly; the
cost is that a runner slot is spent discovering that the review's subject no
longer exists.

Add the workflow-level group keyed by the dispatched pull request, matching
codeql-scan-dispatch.yml's workflow-level group and the rationale recorded in
strix.yml, noema-review.yml and opencode-review.yml. The job-level group stays.
No behaviour changes between two runs that are both executing -- the job-level
group already cancels there; what changes is that a superseded run is now
cancelled while queued.

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

Copy link
Copy Markdown
Contributor Author

Evidence on bf24747a, stated as it stands right now.

  • Targeted run of every file this change touches or is pinned by — test_required_workflow_queue_contract.py, test_opencode_agent_contract.py, test_pr_review_autofix_nvidia_nim_contract.py, test_opencode_rust_coverage_toolchain_contract.py: 143 passed, 1 skipped.
  • Negative control: the new contract test fails against main's workflow and passes on this head (run commit-safe, with the restore asserted afterwards).
  • Full-suite confirmation on this exact commit is still running locally under heavy CPU contention; I will post its figures here when it lands rather than quote an earlier run as if it were this one. The previous full run on this branch was 2927 passed, 1 skipped, 21 subtests, coverage 100% (13181 statements, 5326 branches, 0 missed), interrogate 100%, with three failures that this commit fixes (see below).

Two things the earlier run caught, both now folded in:

  1. test_opencode_agent_contract.test_workflow_provisions_sandbox_tool_and_reviewer_agent located the review job's concurrency group by splitting on the first concurrency: in the file. With a workflow-level block added, that split reads the new one. The test now addresses the job-level group by its indentation (\n concurrency:\n runs-on:) and keeps all six of its original assertions, so it still pins the job group's key shape (no head-SHA scoping, no pull_request event fields, needs.* outputs).
  2. Changing the workflow changed its blob SHA, so REVIEW_DISPATCH_BLOB_SHA in test_pr_review_autofix_nvidia_nim_contract.py and its paired guard test_review_dispatch_blob_sha_stays_paired_with_trusted_workflow both failed — the guard doing exactly the job it exists for. The pin is updated to fd0e5ff75d9ae6ec68cc6945174dff5c9d487662 and asserted equal to git hash-object on the file in the same run.

Review roles for this one: I authored it, a peer is verifying the head independently, and a third session merges — the same separation used for the four sidecar changes that landed today.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 3ce2d87d-895e-453b-bf06-c38bf755b167

📥 Commits

Reviewing files that changed from the base of the PR and between 4302463 and bf24747.

📒 Files selected for processing (5)
  • .github/workflows/opencode-review-dispatch.yml
  • CHANGELOG.md
  • tests/test_opencode_agent_contract.py
  • tests/test_pr_review_autofix_nvidia_nim_contract.py
  • tests/test_required_workflow_queue_contract.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

opencode-review-dispatch.yml에 워크플로 수준 concurrency를 추가했습니다. 동일 PR의 이전 dispatch 실행을 대기 중 취소합니다. 관련 계약 테스트, 변경 로그, 고정 blob SHA를 갱신했습니다.

Changes

리뷰 디스패치 동시성

Layer / File(s) Summary
워크플로 수준 디스패치 취소
.github/workflows/opencode-review-dispatch.yml, CHANGELOG.md
대상 저장소와 PR 번호로 워크플로 동시성 그룹을 구성합니다. cancel-in-progress: true로 이전 dispatch 실행을 취소합니다.
동시성 계약 검증 갱신
tests/test_required_workflow_queue_contract.py, tests/test_opencode_agent_contract.py, tests/test_pr_review_autofix_nvidia_nim_contract.py
워크플로 수준 및 job 수준 concurrency 블록을 검증합니다. 워크플로 구조에 맞게 계약 추출 기준과 고정 blob SHA를 갱신합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to bf247

This change coalesces superseded review dispatches for the same pull request before runner admission while retaining the existing job-level safeguards. The workflow behavior is covered by updated contract checks, with no merge-blocking risk identified.

Suggested reviewers: claude, cursoragent

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files. (2 skipped: 2 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 동일 PR의 대기 중인 OpenCode 리뷰 디스패치를 병합하는 주요 변경을 정확하고 간결하게 설명합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dispatch-workflow-level-concurrency-20260906

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

Comment on lines +30 to +34
group: >-
opencode-review-dispatch-${{
github.event.client_payload.target_repository || github.repository }}-${{
github.event.client_payload.pr_number || github.run_id }}
cancel-in-progress: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Stale reruns cancel current reviews

Rerunning an older dispatch enters the same PR-wide concurrency group and cancels the current-head review before metadata validation. The stale rerun then fails validation, leaving no review to publish or wake the required check.

Prompt for agents
The workflow-level concurrency group treats the newest workflow run as authoritative, but a GitHub rerun preserves the older repository_dispatch payload. Rerunning a stale dispatch for the same target repository and PR therefore cancels an in-progress current-head dispatch before validate-pr-metadata can reject the stale payload. The valid dispatch is lost and no replacement is created. Adjust workflow-level admission so cancellation is based on validated target-head evidence or otherwise ensure that canceling a current-head dispatch always enqueues exactly one replacement, while retaining pre-runner coalescing for genuinely superseded heads. Cover the stale-rerun-versus-current-dispatch case in the workflow contract tests.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Full-suite confirmation on bf24747a, as promised. GITHUB_ACTIONS=true, local: 2929 passed, 1 skipped, 21 subtests; coverage report --fail-under=100100% (13181 statements, 5326 branches, 0 missed); interrogate100%. Negative control re-run against origin/main's workflow with this head's tests: the new contract test is the only failure (1 failed, 57 deselected), and the restore was asserted afterwards (workflow-level group present again, working tree clean).

@seonghobae

Copy link
Copy Markdown
Contributor Author

Verification (lane host 1) — independent execution, not a relay

Head confirmed by git ls-remote origin refs/pull/1958/head = bf24747a10ae691a7589972bf44fe8a8a80b5290; origin/main (43024633) is an ancestor, so the branch is 0 behind. Three-dot diff against main: 5 files, +69 / −3.

(a) Workflow-level and job-level groups coexisting

Consistent with the 2026-09-05 design rather than an exception to it. All three required review workflows already carry a workflow-level concurrency: block, and codeql-scan-dispatch.yml — the sibling repository_dispatch handler — keys its workflow-level group on exactly the same expressions this PR adds:

codeql-scan-dispatch-${{ github.event.client_payload.target_repository || github.repository }}-${{ github.event.client_payload.pr_number || github.run_id }}-${{ ... }}

The file now holds exactly one workflow-level group and one job-level group (counted, not eyeballed). The job-level group keeps its needs.validate-pr-metadata.outputs.* keying, which is the one that can only be evaluated after admission; the new group is what coalesces runs before a runner is allocated.

(b) cancel-in-progress: true and the sweep-job deadlock risk

Not applicable here: opencode-review-dispatch.yml defines four jobs — validate-pr-metadata, coverage-source-tree, coverage-evidence, opencode-review-target — and none of them is a queue sweep or cancel job. The cancel-superseded-* jobs that would create the self-cancellation hazard live in other workflows.

(c) The test-slice change does not narrow the contract

The new slice is 216 characters and contains exactly the review job's group plus its cancel-in-progress, with no job header inside it — so it cannot pass by matching text belonging to a different job. All six original assertions are present and unchanged (two positive on needs.validate-pr-metadata.outputs.*, four negative on format('pr-{0}-{1}', client_payload.pr_head_sha, client_payload.pr_number, github.event.pull_request).

The change was also necessary, not cosmetic: with the workflow-level block added, the old split("concurrency:") slice starts at the new block's comment, so the negative assertion on github.event.client_payload.pr_number would have been evaluated against the workflow-level group that legitimately contains that expression. The guard fired for the right reason.

(d) Trust boundary of the client_payload key

The group key is attacker-influenced in the sense that it comes from the dispatch payload, and cancellation happens at run creation, before validate-pr-metadata reads vars.OPENCODE_REPOSITORY_DISPATCH_ACTOR (that variable is first referenced at line 138, inside a job). So an actor who can send a repository_dispatch to this repository can cause a legitimate in-flight dispatch run for a pull request to be cancelled, even though their own run is rejected moments later.

I do not consider this a blocker, for two reasons stated plainly rather than assumed: sending repository_dispatch already requires a token with write access to this repository, so the actor is inside the trust boundary already; and codeql-scan-dispatch.yml has carried exactly this property, with the same key, since it was adopted. It is worth recording as a known property of payload-keyed admission rather than discovering it later.

Coupling points the author flagged

  1. test_opencode_agent_contract — covered under (c). The contract is preserved, not narrowed.
  2. REVIEW_DISPATCH_BLOB_SHA — recomputed independently: git hash-object .github/workflows/opencode-review-dispatch.yml on this head returns fd0e5ff75d9ae6ec68cc6945174dff5c9d487662, which is the value pinned at tests/test_pr_review_autofix_nvidia_nim_contract.py:20. Match.

Numbers from my own run

check result
full gate on this head 2929 passed, 1 skipped, 21 subtests
coverage 100%, 13181 statements, 0 missed
interrogate 100%
negative control (origin/main's workflow + this head's tests, 3 files) 2 failed, 133 passed

The negative control fails test_privileged_review_dispatch_coalesces_superseded_runs_before_admission (the new contract) and test_independent_review_agent_workflow_matches_reviewed_blob (the blob pin, necessarily, since the workflow content is swapped). The author reported one failure from a deselected subset; both readings are consistent, and the two-failure figure is what the unfiltered run of those three files produces. After restoring the head's workflow, git status --porcelain reported 0 modified files and the blob hash returned to fd0e5ff7….

Verdict: verified. No blocking findings. The trust-boundary property in (d) is recorded, not held against this change.

🤖 Verified by Claude Code

@seonghobae
seonghobae merged commit 5ea1cc4 into main Sep 6, 2026
6 of 17 checks passed
@seonghobae
seonghobae deleted the fix/dispatch-workflow-level-concurrency-20260906 branch September 6, 2026 11:24
@seonghobae

Copy link
Copy Markdown
Contributor Author

Merged as 5ea1cc47 (squash, bypass over REST) — author session peer 1 (Workflow 통합 및 contextual-orchestrator 개선), merger a separate session.

Verified by the merging session's own run, not relayed. Because main had moved since this PR's head was verified, the merged tree was gated, not just the head: git merge-tree of 43024633 × bf24747a — no conflicts, no markers; 5 files +69/−1... (recomputed by me: +69/−3) — .github/workflows/opencode-review-dispatch.yml +22, tests/test_required_workflow_queue_contract.py +36, tests/test_opencode_agent_contract.py +8/−2, tests/test_pr_review_autofix_nvidia_nim_contract.py +2/−1, CHANGELOG.md +4; full gate on that merged tree: 2929 passed, 1 skipped, 21 subtests passed in 217.46s (0:03:37); coverage 100%; RESULT: PASSED (minimum: 100.0%, actual: 100.0%). After the squash, main's tree a392e96c1fa3 is byte-identical to the gated merge tree a392e96c1fa3. Negative control on the head: run by the merging session, not relayed — on the head's own tree with main's opencode-review-dispatch.yml swapped in, the two contract modules give 1 failed / 111 passed, the failure being test_privileged_review_dispatch_coalesces_superseded_runs_before_admission; with the head's own workflow, 112 passed. The authors' counts differ only by filter scope (peer1 selected the new contract alone, host1 ran the files whole and so also caught the blob-pin guard), which is not a contradiction. What it does: opencode-review-dispatch.yml carried a concurrency group only at job level, and GitHub never evaluates a job-level group while the whole run waits behind the organization job ceiling. Superseded dispatches for one pull request therefore coalesced only after each had already been allocated a runner. Measured on 2026-09-06: of the five dispatch runs that passed validate-pr-metadata, four were rejected hours later by the privileged metadata check because the head had moved while they queued (runs 34002473295, 34010256951, 34015973300, 34016922761), each after coverage-source-tree and coverage-evidence had already run; the fifth (34011098364, #1947) matched head and base exactly and was rejected only because the pull request had merged. This adds the workflow-level group, keyed on the target pull request with github.run_id as the fallback so payload-less runs stay in their own groups, matching codeql-scan-dispatch.yml and the reason recorded in strix.yml. The job-level group stays.

Why cancellation cannot strand a live review: review_admission_controller.plan_dispatches keys each lease on an identity containing the head SHA and rejects a repeat for the same head as idempotent, marking earlier queued records for that stream stale only when a new head is admitted. Two dispatch runs exist for one pull request only when the head actually moved, so the group cancels superseded heads and never a review of the current one. That guarantee covers senders that take an admission lease; opencode-review.yml posts its own dispatch without one and skips only when a verdict already exists, so widening the actor allowlist in #1929 must wait until that request is idempotent for an in-flight head. The pull request body records this ordering.

Recorded as pre-existing, not introduced here: the group key comes from client_payload and cancellation happens at run creation, while the dispatch actor allowlist is evaluated inside the job, so an actor able to send a dispatch can cancel another legitimate in-flight run for that pull request before its own run is rejected. Sending a dispatch already requires a write token on this repository, and codeql-scan-dispatch.yml has held the same property under the same key shape; noted so it is not later mistaken for a regression from this change.

Authorization, cited at the act from the standing directive: "60-job ceiling에 막혀 있을 거라 일을 하기 어렵다면 60-job ceiling을 만드는 workflow issues를 추적해서 해소하세요. 이 상황은 Chicken-and-eggs 상황이므로 Bypass merge가 허용됩니다." This is the tracing-and-clearing half of that grant: stale dispatches were occupying runner slots for hours and then discovering their subject no longer existed, which is the ceiling pressure the directive names, and the change is confined to a review-pipeline workflow plus its contract tests and CHANGELOG. It cannot demonstrate itself before merge, since the coalescing it adds is evaluated at run creation by main's copy of the workflow. No pinned or policy file; not dirty. Author peer 1, verification host 1, merge by this session: three separate sessions.

seonghobae pushed a commit that referenced this pull request Sep 6, 2026
Both changelog entries kept: this branch's shared-runner quality gate stays on
top as the not-yet-merged change, with #1958's dispatch-concurrency entry
below it in merge order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
seonghobae added a commit that referenced this pull request Sep 6, 2026
Resolves the CHANGELOG.md prepend collision with #1958 by keeping both
sections: main's "Superseded OpenCode review dispatches coalesce before
they take a runner" is preserved verbatim and this branch's "Required
status context guard" section is re-prepended above it. Both PRs insert
at line 1, which git cannot auto-merge.

Verified after resolution: no conflict markers, main's CHANGELOG lines
all present (0 dropped), this branch's section appears exactly once, and
the diff against origin/main is unchanged at CHANGELOG.md +5 and
tests/test_branch_protection_required_context_jobs.py +130.

Gates on the merged tree: 2932 passed / 1 skipped, coverage 100% (0
missed), interrogate 100%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
seonghobae added a commit that referenced this pull request Sep 6, 2026
Resolves the CHANGELOG.md prepend collision with #1957 by keeping both
sections: main's "Review sidecar preflight postpones a rate-limited
account's candidates" is preserved verbatim, along with #1958's section
merged in the previous round, and this branch's "Required status context
guard" section is re-prepended above them.

Verified after resolution: no conflict markers, 0 of main's CHANGELOG
lines dropped, this branch's section present exactly once, both incoming
sections preserved, and the diff against origin/main unchanged at
CHANGELOG.md +5 and tests/test_branch_protection_required_context_jobs.py
+130. #1957 touched no workflow file, so no pinned job name moved.

Gates on the merged tree: 2949 passed / 1 skipped / 21 subtests,
coverage 100% (0 missed), interrogate 100%.

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

Copy link
Copy Markdown
Contributor Author

Acceptance observed — and it is narrower than this PR claimed

The acceptance sample this PR needed appeared 5.9 hours after it merged, on ContextualWisdomLab/.github#1923.

older  34035613406   created 13:16:51Z   cancelled 15:00:32Z
newer  34040933417   created 15:00:30Z
                     cancellation lag: +2 s

Two seconds. For contrast, the pre-merge baseline recorded on this PR had 24 overlapping same-pull-request
pairs and zero coalesced; the five whose older run ended cancelled lagged 0.7 to 2.9 hours
behind the newer run's creation, which is a sweep. A two-second lag is not.

It was the workflow-level group, not the pre-existing job-level one

The decisive detail is which jobs died:

validate-pr-metadata   success    steps=4   14:17:05Z → 14:17:09Z
coverage-source-tree   cancelled  steps=0   14:17:10Z → 15:00:31Z
coverage-evidence      cancelled  steps=0             → 15:00:31Z
opencode-review        cancelled  steps=0             → 15:00:31Z

coverage-source-tree is not inside the job-level opencode-review-… group this workflow already
had; only the review job is. Cancelling it requires a run-level cancellation, which is what a
workflow-level concurrency block does. So the group this PR added is what fired.

What this does not show

This PR's own description said a superseded dispatch would be "discarded while queued, before it takes
a runner". That is not what happened here. The older run:

queued   60.2 min   (created → first job start)
running  43.5 min   (first job start → cancellation)

It had already been allocated a runner and was 43 minutes into coverage-source-tree when the sibling
arrived. The workflow-level group can only act at the moment the sibling is created; if the older run
reached a runner first, that time is already spent. The "before it holds a slot" benefit applies only
when the sibling arrives during the older run's queue phase — which is the common case at this
workflow's arrival rate, but it is not what this sample demonstrates.

So the accurate claim is narrower than the one I wrote when merging: the workflow-level group
coalesces superseded runs at the sibling's creation instant, whereas the job-level group coalesced
none.
Whether the discarded run had reached a runner is a matter of timing, not of the fix.

The other candidate is not a sample

#1879 also had two post-merge dispatches, and its older run is cancelled — but the lag is
−6277 s: it was cancelled 1.7 hours before the newer run was created. That is the sweep
signature, and counting it would have inflated the result. Two of the four post-merge multi-dispatch
groups (#1938, #1962) show no cancellation at all.

One sample. It settles that the mechanism fires; it does not measure how often.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Re-correcting the comment above: the older run never held a runner

My previous comment narrowed this PR's claim on the grounds that the coalesced run "had already been
allocated a runner and was 43 minutes into coverage-source-tree". That is wrong. It never received a
runner for that job.

The jobs API reports allocation directly. On the coalesced run 34035613406:

validate-pr-metadata   success    steps=4   runner_id=1001715207  "GitHub Actions 1001715207"     4.0 s
coverage-source-tree   cancelled  steps=0   runner_id=0           ""                           2601.0 s
coverage-evidence      cancelled  steps=0   runner_id=null        null
opencode-review        cancelled  steps=0   runner_id=null        null

Splitting on that field: 4.0 s of runner time, 43.4 min of queue residency. coverage-source-tree
asked for an ubuntu-24.04 runner and never got one; its 2601 seconds are time spent in line, not
time spent executing.

So the original description was closer to right than my correction: the superseded dispatch was
discarded before it took a slot. The precise version is that its 4-second gate job had completed, and
the three jobs actually waiting for runners were discarded at the sibling's creation. The 43.4 minutes
are reclaimed queue position, not reclaimed runner time.

Everything else in the previous comment stands — the +2 s cancellation lag, the run-level cancellation
proving it was the workflow-level group, #1879's −6277 s lag disqualifying it as a sample, and the
fact that this is one observation of the mechanism rather than a measurement of its frequency.

The instrument point, since it cost two wrong statements today

started_at is not evidence that a runner was allocated. A job cancelled while queued still carries
one, and completed_at − started_at then reads exactly like execution time. steps == 0 hints at it;
runner_id settles it — integer and name when allocated, 0/"" when requested and never granted,
null when never scheduled.

The same derived-proxy mistake produced a 118.4 h "occupancy" figure I published on #1800, which the
same split shows is 41.3 h of runner time and 77.2 h of queue residency. Corrected there too.

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