Skip to content

fix(actions): coalesce superseded agent mentions while they are queued - #1964

Merged
seonghobae merged 2 commits into
mainfrom
fix/agent-mention-dispatch-workflow-level-concurrency
Sep 6, 2026
Merged

fix(actions): coalesce superseded agent mentions while they are queued#1964
seonghobae merged 2 commits into
mainfrom
fix/agent-mention-dispatch-workflow-level-concurrency

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Both agent-mention dispatchers carried their concurrency group on the single validate-and-forward
job. A job-level group is not evaluated while the whole run waits behind the organization job
ceiling, so a superseded mention keeps its queue slot until a runner frees up and only cancels once
it is already running. Moving the group to workflow level coalesces the older run while both are
still queued, which is where the slot is actually held. Same defect and same fix as #1958.

Evidence that the defect is real

Measured on opencode-review-dispatch.yml, which carried the identical defect until #1958, over the
39.7 hours ending 2026-09-06T12:41Z. Runs grouped by the FULL <owner>/<repo>#<pr> parsed from the
run name -- the central dispatcher runs in .github on behalf of sibling repositories, so grouping
on the bare number merges different pull requests.

measure value
adjacent same-group pairs 89
pairs overlapping in time (older still open when the newer was created) 24
... older run ended cancelled 5
... cancelled within 120 s of the newer's creation (i.e. coalesced) 0
overlap arrival rate 0.60 / h

The five cancellations lagged +0.7, +1.2, +2.1, +2.8 and +2.9 hours behind the newer run's creation.
Coalescing puts that lag at roughly zero; hours means a sweep. The other 18 overlapping runs ran to
failure while already superseded, the longest living 9.4 hours past its successor's arrival
(#1529).

An overlap count of 24 rather than 23 is a correction from a peer session: a run that has not
concluded was open at every earlier instant, so older.created_at < newer.created_at settles the
overlap and no end time is needed. My first pass compared updated_at for every pair, which both
used a field that carries no liveness information for a queued run -- across 100 currently queued
runs the updated_at - created_at offset is 0 s for 80, 1 s for 10 and 2 s for 10, never above 60 s
-- and let a 34-second margin decide whether a row entered the sample.

What is deliberately not changed

agent-mention-router.yml keeps its two job-level groups. It has two jobs with different groups and
its organization sweep sets cancel-in-progress: false on purpose. A workflow carries at most one
workflow-level group, so hoisting either would hand the sweep the route's cancelling policy and let a
later comment kill a sweep part way through the organization. test_agent_mention_router_keeps_its_two_distinct_job_groups pins this.

The nine central workflows with cancel-in-progress: false are also untouched. Every one of them
mutates state (merge scheduler, autofix, hourly repair, Cloudflare DNS, SBOM scheduler), where a
mid-flight cancel leaves partial state. false there is the safe design, not a defect.

The retargeted contract

Hoisting failed test_agent_mention_downstream_idempotency.py, which carried a flat
assert "concurrency:" not in header. git log -S on that assertion shows it arrived with
109d79b7 ("replace unsupported queue concurrency"), which deleted a workflow-level block using
queue: max -- a key GitHub Actions does not support -- and parked the group on the job on the way
past. What that commit pins is the absence of queue:, not the level; five other queue-bearing
workflows here (strix.yml, noema-review.yml, opencode-review.yml, codeql-scan-dispatch.yml,
opencode-review-dispatch.yml) key their groups at workflow level.

So the assertion is retargeted rather than deleted, to assert "queue:" not in header plus a
job-level absence check. The group text itself is unchanged and stays on one line, so the exact
string that test pins still matches verbatim.

Controls

  • Move either group back to the job: the retargeted contract and the new test fail.
  • Reintroduce queue: max at workflow level: the retargeted contract still fails, so 109d79b7's
    actual finding remains pinned.
  • Both controls were run against a committed tree and the tree restored with git checkout HEAD --.

Gate

coverage run -m pytest tests 2959 passed, 1 skipped, 21 subtests passed; coverage 100%;
interrogate 100%.

Developer experience

A superseded mention no longer occupies a runner slot for hours before being discarded, so the
queue drains faster for everyone waiting behind it. The retargeted assertion keeps the original
queue: max prohibition enforced, so a future session cannot reintroduce the unsupported key.

User experience

An @-mention that is superseded by a newer comment stops producing a second, stale agent response.
Reviews arrive against the comment the author actually meant.

🤖 Generated with Claude Code


Devin Review

Both agent-mention dispatchers carried their concurrency group on the single
`validate-and-forward` job. A job-level group is not evaluated while the whole
run waits behind the organization job ceiling, so a superseded mention keeps its
queue slot until a runner frees up and only cancels once it is already running --
which is after the slot has been held, not instead of holding it. Moving the
group to workflow level coalesces the older run while both are still queued.

Measured on `opencode-review-dispatch.yml`, which carried the identical defect
until #1958, over the 39.7 hours ending 2026-09-06T12:41Z: 24 pairs of runs for
one pull request overlapped in time, and none was coalesced. The five whose older
run ended `cancelled` were cancelled 0.7 to 2.9 hours after the newer run was
created, which is a sweep rather than concurrency; the other 18 ran to `failure`
while already superseded, the longest living 9.4 hours past its successor's
arrival (#1529).

`agent-mention-router.yml` is deliberately left alone: it has two jobs with
different groups, and its organization sweep sets `cancel-in-progress: false` on
purpose. A workflow carries at most one workflow-level group, so hoisting either
one would give the sweep the route's cancelling policy and let a later comment
kill a sweep part way through the organization. A test now pins that.

The group text is unchanged and stays on one line, so the string
`test_agent_mention_downstream_idempotency.py` pins still matches verbatim. That
test's `assert "concurrency:" not in header` is retargeted rather than deleted:
`git log -S` shows it arrived with 109d79b ("replace unsupported queue
concurrency"), which removed a workflow-level block using `queue: max` -- a key
GitHub Actions does not support -- and parked the group on the job on the way
past. What that commit pins is the absence of `queue:`, not the level, so the
assertion now reads `assert "queue:" not in header` plus a job-level absence
check. Controls both ways: reverting to job level fails the retargeted contract
and the new test; reintroducing `queue: max` still fails the retargeted contract.

Gate: 2959 passed, 1 skipped, coverage 100%, interrogate 100%.

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

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 24 minutes.

Check out review usage here.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: c2e3775e-237f-4f7a-9d19-61f2d2c7a7ad

📥 Commits

Reviewing files that changed from the base of the PR and between ee5567f and 778fea0.

📒 Files selected for processing (4)
  • .github/workflows/agent-mention-noema-dispatch.yml
  • .github/workflows/agent-mention-opencode-dispatch.yml
  • tests/test_agent_mention_downstream_idempotency.py
  • tests/test_required_workflow_queue_contract.py

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 +22 to +23
group: agent-mention-noema-${{ github.event.client_payload.target_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.

🟡 Delayed mentions replace newer requests

When the sweep rediscovers an older unclaimed comment, cancel-in-progress lets its delayed dispatch cancel a newer request. The concurrency key lacks comment ordering, so arrival order decides which response survives.

Prompt for agents
The workflow-level PR group coalesces by dispatch arrival, but agent_mention_sweep.py can redispatch any older comment that has no artifact. A wrapper canceled while queued creates no artifact, so an older comment can arrive after and cancel a newer invocation for the same PR. Update both agent-mention wrapper workflows and the router/sweep contract so cancellation is based on source-comment ordering, not dispatch arrival, while retaining workflow-scope admission and exact-invocation artifact idempotency. Add a regression covering a delayed older sweep dispatch arriving after a newer local dispatch.
Devin Review

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

…urrency

Both sides added tests in the same region of
tests/test_required_workflow_queue_contract.py; the resolution keeps both.

This branch's two assertions move onto workflow_level_concurrency_group(), the
helper #1970 introduced, rather than staying on the raw concurrency block.
Correcting a claim I made when merging #1970: this branch's comment block does
NOT quote the expressions its tests assert on -- it names other workflow files
and explains the mechanism in prose -- so a collapsed key here would have been
caught by the raw-block form too. The move is defence against a future comment
that does quote them, not a fix for a present hole.

Control on the prospective shape (collapse the key, move the expressions into
the comment beside it): the helper form fails, while all four raw-block
assertions -- group prefix, target_repository, pr_number || github.run_id, and
cancel-in-progress: true -- would have passed.

cancel-in-progress is a sibling key and therefore outside the group value, so it
is asserted separately, anchored to its own line at the block's indent where a
comment cannot satisfy it.

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

Copy link
Copy Markdown
Contributor Author

Resolved against main after #1970 merged (ee5567f7). New head 778fea06cec2e1b6380d923a0dbe5039b68e9641.

Resolution

Both sides added tests in the same region of tests/test_required_workflow_queue_contract.py; the
resolution keeps both. All four new tests are present — this branch's
test_agent_mention_dispatch_coalesces_while_queued and
test_agent_mention_router_keeps_its_two_distinct_job_groups, and #1970's
test_concurrency_group_slice_ignores_the_comment_that_documents_it and
test_concurrency_group_slice_reads_a_folded_multi_line_key.

This branch's assertions move off the raw concurrency block and onto
workflow_level_concurrency_group(), the helper #1970 introduced. cancel-in-progress is a sibling
key and therefore outside the group value, so it is asserted separately and anchored to its own line
at the block's indent, where a comment cannot satisfy it.

Correcting something I claimed when merging #1970

I said there that this branch's comment blocks quote the expressions its tests assert on, making them
a live instance of the defect #1970 closes. That is false. Searching the comment lines alone:

github.event.client_payload.target_repository            False
github.event.client_payload.pr_number || github.run_id   False
cancel-in-progress: true                                 False

The comments name other workflow files and explain the mechanism in prose. A collapsed key here would
have been caught by the raw-block form too. I found this because the mutant I wrote to demonstrate the
claim failed its own precondition, and I have posted the retraction on #1970.

So the move onto the helper rests on a weaker, accurate footing: defence against a future comment that
does quote the expressions, not repair of a present hole.

Control

Prospective shape — collapse the key to the repository alone and move the expressions into the comment
beside it:

resolved (helper form)      1 failed
raw-block assertions        group prefix              True
                            target_repository         True
                            pr_number || github.run_id True
                            cancel-in-progress: true  True    -> all four would have passed

Gate

coverage run -m pytest tests on the resolved tree: 2961 passed, 1 skipped, 21 subtests
(2958 on main plus this branch's three); coverage 100%; interrogate 100%.

@seonghobae

Copy link
Copy Markdown
Contributor Author

교차 세션 검증 — 네 축 통과, 후속 범위 하나 발견

저자(peer 세션)와 분리된 세션이 778fea06 을 검증했습니다. 같은 GitHub 정체성이라 Approve 는 422 로 거부되므로 근거만 남깁니다.

저자 철회 재확인

저자가 "이 브랜치의 주석이 단언 표현식을 인용해 방패가 된다"는 앞선 주장을 철회했는데, 독립으로 확인했습니다. 두 워크플로의 주석 줄만 추출해 세 표현식을 검색한 결과 여섯 조합 전부 False 입니다. 철회가 맞고, 이 PR 의 근거는 "현재 수리" 가 아니라 "장래 방어" 입니다.

축별 결과

해소 범위 — 해소 커밋의 이전 헤드(d49d5d9c) 대비 워크플로 파일 변경 0줄. 바뀐 건 tests/test_required_workflow_queue_contract.py(+150/-34)와 tests/test_codeql_scan_dispatch_workflow_contract.py(+15)뿐입니다.

헬퍼 이전 — 이 PR 의 두 테스트는 workflow_level_concurrency_group() 을 씁니다.

판별력 — 저자 변이를 재사용하지 않고 독립 변이 둘을 만들어 실행했습니다. 기준선 63 passed.

변이 조작 결과
A agent-mention-noema-dispatchcancel-in-progress 를 주석으로 위장하고 실제값을 false coalesces_while_queued[noema] 1 failed
C group 을 저장소 단위로 붕괴하고 원래 표현식을 주석으로 이동 coalesces_while_queued[noema] 1 failed

줄 앵커((?m)^ cancel-in-progress: true$)가 주석을 막고, group.strip().startswith(prefix) 가 키 안을 실제로 검사합니다.

후속 범위 — 이 PR 의 결함은 아닙니다

같은 파일에서 concurrency_contract여전히 6곳에서 원시 슬라이싱으로 만들어집니다(136·268·448·578·830·904행). 헬퍼는 4곳에서만 쓰입니다. 그래서 범위를 넓힌 변이를 하나 더 돌렸습니다:

변이 B  noema-review.yml 의 cancel-in-progress 를 주석으로 위장 + 실제값 false
        → 63 passed. 아무것도 잡지 못함.

필수 리뷰 워크플로의 cancel-in-progress 를 통째로 뒤집었는데 계약이 초록입니다. test_noema_triggers_preserve_standalone_pull_request_review 에 같은 구멍이 남아 있고, 578행(strix)·448·830행(루프)·136행도 같은 형태입니다.

cancel-in-progress 는 그룹 값 밖이라 헬퍼로 덮이지 않으므로, 이 PR 이 쓴 줄 앵커 형태를 나머지 호출부에도 확산하는 것이 후속 작업입니다.

게이트

uvx ruff check --select F821 All checks passed · 전체 스위트 2961 passed / 0 failed · coverage TOTAL 100% · interrogate PASSED. 변이 셋 전부 원복 확인, 작업트리 청결.

검증하지 않은 것

변이 A·C 는 agent-mention-noema-dispatch.yml 한 파일에만 적용했습니다. opencode 쪽은 파라미터가 대칭이라고 가정했을 뿐 따로 실행하지 않았습니다.

판정: 병합 가능. 위 후속 범위는 이 PR 을 막는 사유가 아닙니다.

🤖 Generated with Claude Code

@seonghobae
seonghobae merged commit 6e014c9 into main Sep 6, 2026
4 of 16 checks passed
@seonghobae
seonghobae deleted the fix/agent-mention-dispatch-workflow-level-concurrency branch September 6, 2026 15:20
@seonghobae

Copy link
Copy Markdown
Contributor Author

Merged as 6e014c9bae22f1e6d8302a4f1cc38f4f6a114ae2.

Gated on the merged tree, not on the head. git merge-tree --write-tree main 778fea06 produced 8dd745b817bc699339c672ce9ed5be4f33804fa8 with a clean exit, and that tree is what was tested:

check result
pytest 2961 passed, 1 skipped, 21 subtests
coverage (scripts/ci) 100%, 0 missed of 13181 statements
interrogate 100%
conflict markers outside tests/ 0

Tree identity proven after the merge. The squash commit's tree is 8dd745b817bc699339c672ce9ed5be4f33804fa8, byte-identical to the gated tree, so what landed on main is exactly what passed. Head and base were both re-read from the remote immediately before the merge and were unmoved, and the merge was pinned with sha=778fea06.

Change verified as described. Both workflows now carry the group at workflow level with cancel-in-progress: true, and the job-level block is gone rather than duplicated. Parsed from the merged tree, not from the diff.

Why this merged while blocked. Of the 12 required contexts on main, 9 never reported and 3 were queued. That is the queue blockage this pull request exists to reduce, so waiting on those contexts cannot be satisfied. The pull request was MERGEABLE, never dirty.

Independent field evidence for the mechanism. Measured on the already-shipped workflow-level group in opencode-review-dispatch.yml: for .github#1923, a second dispatch created at 15:00:30Z cancelled the older run at 15:00:32Z, two seconds later, with the downstream jobs at steps=0, meaning no runner ever started them. No other run in the surrounding two minutes was cancelled, so a batch sweep is ruled out. That is the coalescing this change extends to the agent-mention path.

Author and verifier were other sessions; this session only merged.

🤖 Addressed by Claude Code

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