Skip to content

fix(ci): give a dispatched run its own concurrency group - #5048

Merged
lidge-jun merged 1 commit into
devfrom
codex/5037-dispatch-concurrency-group
Sep 18, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/5037-dispatch-concurrency-group

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Summary

A dispatched run shared its concurrency group with pushes, so any merge into dev cancelled it. ci.yml keyed the group on github.ref alone with cancel-in-progress: true, and a workflow_dispatch against dev has the same github.ref as a push to dev.

The lane this made uncompletable is macos control, the longest job here at roughly fifty minutes. Run 35318264610 was cancelled in the same second its job started, three minutes after being queued — a concurrency cancellation at startup, not a timeout. On a branch receiving merges the probability that none lands inside a fifty-minute window is close to zero, so a maintainer dispatching the lane for release evidence usually got nothing back, and had no signal that they had: a cancelled job reports neither pass nor fail. This is why the lane produced cancellations for months and why they were read as runner capacity; raising its budget from 30 to 75 minutes in #5028 did not change it.

After this change:

  • push and pull_request are unchanged. Superseding an older head on the same ref is exactly right for them, and it is what keeps nine Windows shards and two macOS shards from running for an answer nobody will read.
  • workflow_dispatch is keyed on github.run_id, which is unique per run, so every dispatch is a group of one. It cancels nothing, nothing cancels it, and two probes of the same ref no longer queue behind each other — they are two questions, not a revision of one.

This retires the frozen-ref workaround (ci/control-probe-2590 at 56a99d3848), which existed only to get the lane a ref that nothing else pushes to.

Closes #5037.

Verification

tests/ci-workflows/ci-concurrency-groups.test.ts is new and evaluates the two expressions rather than matching their text, because a spelling assertion would pass on any rewrite that kept the words. It implements the narrow grammar ci.yml uses — ${{ ... }} interpolation, github.<field> lookups, single-quoted literals, ==/!=, and the a && b || c ternary — and refuses anything outside it, so an expression that later grows a function call fails loudly instead of being silently mismodelled. There is a case asserting that refusal, so the evaluator is not vacuous.

The four contracts it pins: a dispatch and a push on refs/heads/dev resolve to different groups and the dispatch is not cancellable; two dispatches of the same ref resolve to different groups; push and pull_request still share a group per ref, differ across refs, and stay cancellable; and the workflow declares exactly the three triggers these cases cover, so a fourth cannot inherit the push answer by accident.

No existing test pinned ci.yml's concurrency block. The cancel-in-progress: true assertions in tests/ci-workflows/ci-workflows.test.ts belong to service-lifecycle.yml and release.yml and are untouched.

Hosted CI on this branch is the check; no local suite was run.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

This touches .github/workflows/, which MAINTAINERS.md puts behind security review. The change adds no permission, no secret, no new trigger, and no checkout of head-controlled code: it only alters which runs cancel which. Workflow permissions: contents: read is unchanged.

Summary by CodeRabbit

  • Chores

    • Updated CI workflow run handling so manually triggered runs proceed independently, while push and pull-request runs continue to replace outdated runs for the same branch or change set.
  • Tests

    • Added coverage to verify CI concurrency behavior across manual runs, pushes, pull requests, branches, and configured triggers.

A `workflow_dispatch` against `dev` landed in the same concurrency group as
the `push` runs on `dev`, because the group was keyed on `github.ref` alone
and `cancel-in-progress` was unconditionally true. The next merge therefore
cancelled the dispatch. Run 35318264610 was cancelled in the same second its
job started, three minutes after it was queued.

The lane this hurt is `macos control`, the longest job in the workflow at
roughly fifty minutes, so on a branch under active development the odds that
no merge landed inside its window were close to zero. A maintainer
dispatching it for release evidence usually got nothing back and had no
reason to notice, because a cancelled job reports neither pass nor fail.
Those cancellations were read as runner capacity for months; raising the
budget from 30 to 75 minutes in #5028 did not change them.

Supersession is still what `push` and `pull_request` want, so they keep it.
A dispatch is keyed on `github.run_id` instead, which is unique per run, so
each dispatch is a group of one: it cancels nothing and nothing cancels it,
including a second dispatch of the same ref.

Closes #5037.
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 18, 2026 08:34
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 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-09-18T08:37:38.848407Z a023f5e PR opened
ℹ️ 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 added the bug Something isn't working label Sep 18, 2026
@coderabbitai

coderabbitai Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ac410621-3587-4fc8-a45b-7eddec618e4c

📥 Commits

Reviewing files that changed from the base of the PR and between facd2b6 and a023f5e.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • tests/ci-workflows/ci-concurrency-groups.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The CI workflow now isolates manual dispatch runs by github.run_id and keeps push and pull-request runs grouped by ref. New tests evaluate the expressions and verify cancellation and isolation across supported triggers and refs.

Changes

CI concurrency isolation

Layer / File(s) Summary
Workflow concurrency behavior
.github/workflows/ci.yml lines 63–83
Manual dispatches use github.run_id for their concurrency group and do not cancel in-progress runs. Push and pull-request runs continue to use github.ref and cancel superseded runs.
Concurrency expression validation
tests/ci-workflows/ci-concurrency-groups.test.ts lines 1–109
The tests evaluate the supported expression syntax, validate the workflow triggers, and verify dispatch isolation, unique dispatch groups, same-ref supersession, and cross-ref isolation.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to a023f

Manual dispatches receive independent non-cancelling concurrency groups, while push and pull-request runs retain ref-based supersession.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: assigning each manually dispatched CI run its own concurrency group.
Linked Issues check ✅ Passed The PR satisfies #5037. In .github/workflows/ci.yml, concurrency.group uses github.run_id for workflow_dispatch and github.ref otherwise. cancel-in-progress is false for dispatches and tru…
Out of Scope Changes check ✅ Passed The reviewed changes are limited to .github/workflows/ci.yml and tests/ci-workflows/ci-concurrency-groups.test.ts. The workflow change directly implements #5037. The test change provides automated…
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 1 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 76 / 80

이 PR은 ci.yml의 동시성 그룹을 고쳐서, 메인테이너가 workflow_dispatch로 돌린 긴 레인(특히 macos control)이 dev에 들어오는 다음 merge(push)에 바로 취소되지 않게 만듭니다. 지금 dev HEAD(facd2b6ca, tip #5040, package 2.59.0)의 동시성 설정은 대략 이렇게 되어 있습니다.

group: cross-platform-ci-${{ github.ref }}
cancel-in-progress: true

문제는 단순합니다. workflow_dispatchdev를 돌리면 github.refrefs/heads/dev이고, dev에 PR이 merge되면 그 push도 같은 github.ref를 씁니다. 그래서 두 런이 같은 그룹에 들어가고, cancel-in-progress: true 때문에 merge가 dispatch를 죽입니다. 이슈 #5037이 찍은 증거(run 35318264610)는 job이 시작한 그 초에 cancelled로 끝난 케이스입니다. 타임아웃이 아니라 동시성 취소입니다. #5028이 macos controltimeout-minutes를 75로 올린 뒤에는, “러너가 모자란가?”처럼 보이던 취소의 진짜 원인이 이 공유 그룹이라는 점이 더 분명해졌습니다. 50분짜리 레인 동안 dev에 merge가 하나도 안 들어오길 바라는 건, 활발한 날에는 거의 불가능한 기대입니다. 그리고 cancelled는 pass도 fail도 아니라서, 릴리스 증거용으로 dispatch해 두고도 “결과가 없다”는 사실조차 놓치기 쉽습니다.

이 PR이 바꾸는 규칙은 두 줄입니다. 그룹 키를 github.event_name == 'workflow_dispatch' && github.run_id || github.ref로 두고, cancel-in-progressgithub.event_name != 'workflow_dispatch'로 둡니다. 즉 push/PR은 예전처럼 같은 ref의 옛 헤드를 갈아타고(Windows 9샤드·macOS 2샤드 낭비를 막는 그 절약), dispatch만 run_id로 혼자 그룹을 가져서 아무도 취소하지 않고 아무도에게 취소당하지 않습니다. 같은 ref를 두 번 probe하면 두 개의 질문이 되고, 예전처럼 서로 줄 서서 기다리는 일도 없어집니다. 본문이 말한 frozen-ref 워크어라운드(ci/control-probe-2590)도 이 수정 이후에는 필요 없어집니다. Closes #5037.

테스트 쪽도 방향이 맞습니다. 새 파일 tests/ci-workflows/ci-concurrency-groups.test.ts는 표현식 문자열을 “글자가 그대로 있나”로 검사하지 않고, ci.yml이 쓰는 좁은 문법(${{ }}, github.<field>, 작은따옴표 리터럴, ==/!=, a && b || c)만 평가합니다. 지원하지 않는 term(함수 호출 등)은 throw하게 해 두었고, 그 거부 자체를 케이스로 고정했습니다. 네 가지 계약을 못 박습니다. (1) dispatch와 push가 dev에서 다른 그룹이고 dispatch는 취소되지 않는다. (2) 같은 ref의 dispatch 두 개는 서로 다른 그룹이다. (3) push/pull_request는 여전히 ref당 공유·취소 가능·다른 ref는 분리. (4) 워크플로 on 키가 정확히 push/pull_request/workflow_dispatch 셋뿐이라, 네 번째 트리거가 조용히 push 규칙을 물려받지 못한다. 기존 ci-workflows.test.tscancel-in-progress 단언은 service-lifecycle/release 쪽이라 이 블록을 안 건드린 점도 본문 설명과 맞습니다. 현재 dev의 behaviour-oracle 방향(#4846)과도 잘 맞습니다.

.github/workflows/ 변경이라 AGENTS.md / MAINTAINERS.md의 보안 리뷰 경계에는 걸립니다. 다만 권한·시크릿·새 트리거·헤드 제어 체크아웃을 추가하지 않고, “누가 누구를 취소하느냐”만 바꿉니다. permissions: contents: read도 그대로입니다. types.ts/config.ts 분할 캠페인과도 무관해서 close-don't-rebase 대상이 아닙니다.

라인 62-64 (.github/workflows/ci.yml, 현재 dev) - 지금 HEAD는 group: cross-platform-ci-${{ github.ref }} + cancel-in-progress: true라서 dispatch와 push가 같은 그룹으로 충돌한다. 이 PR이 그 두 줄을 event_name/run_id 분기로 바꾼다.
라인 (PR) concurrency group 식 - event_name == 'workflow_dispatch' && github.run_id || github.ref는 Actions의 a && b || c 관용구이고, 테스트 평가기도 같은 문법만 모델링한다. 나중에 startsWith(...) 같은 함수가 들어가면 테스트가 일부러 깨지도록 되어 있어, 침묵 드리프트보다 낫다.
경로 tests/ci-workflows/ci-concurrency-groups.test.ts - cancel-in-progress를 YAML 파싱 후 string으로 기대한 뒤 render 결과가 "true"/"false" 문자열인지만 본다. Actions 런타임 boolean과 테스트 모델이 어긋나면 여기가 첫 경보다.
경로 workflow.on 트리거 핀 - 네 번째 이벤트(예: schedule)가 추가되면 이 테스트가 깨지도록 잠가 두었다. 의도된 안전장치다.
이슈 #5037 / #5028 - 예산(#5028)과 동시성(#5037)은 다른 축이다. 이 PR은 #5037만 닫고, 75분 예산은 그대로 둔다.

메인테이너의 판단이 필요한 지점

  • 보안 리뷰 한 줄: 워크플로 파일이라 형식상 필요하지만, 변경 범위가 동시성뿐이라 리스크는 낮다. 누가 서명할지.
  • dispatch를 취소 불가로 두면, 같은 날 macos-control을 여러 번 돌릴 때 러너 분(약 50분×N)이 겹칠 수 있다. 의도(증거 유실 방지) 대비 비용이 괜찮은지.
  • frozen-ref 워크어라운드 브랜치/ci/control-probe-*를 이 merge 뒤 정리할지, 문서에 “이제 dispatch만으로 충분하다”고 남길지.

너의 추천
보안 리뷰 한 번 보고 merge. Closes #5037이 본문에 이미 있고, push/PR 취소 동작은 테스트로 고정되어 있으니 rebase나 추가 범위 확장 없이 이대로 랜딩하는 편이 맞다. merge 후 한 번 lane=macos-control dispatch를 dev에 걸어, 그사이 다른 PR이 merge되어도 cancelled가 아닌 완료(또는 진짜 fail)가 나오는지 확인하면 증거가 닫힌다.

이 댓글은 grok-bot이 작성했습니다

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a023f5ef24

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,109 @@
import { expect, test } from "bun:test";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Register the new test in both layout inventories

This new test is absent from both scripts/test-layout/layout.json’s explicit map and tests/fixtures/test-layout-expected.json. Its ci- prefix currently lets the regex seed place it, but that is only the temporary fallback; leaving both inventories unchanged means the authoritative map and its independent oracle do not record the test. Add ci-concurrency-groups.test.ts to both files with the ci-workflows domain.

AGENTS.md reference: AGENTS.md:L23-L27

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

Merging on a recorded macOS exception.

Green at head a023f5ef24: test 1/4 through test 4/4, gates, changes, api usage, storage policy, docker smoke, docs site build, keyring ubuntu, keyring windows, react-doctor, enforce-target, hygiene, label, resolve-pr, select windows runner. The windows N/9 shards are dispatch-only and skipped on pull_request, as usual.

macos 1/2 is queued and macos 2/2 is in progress, behind the macos control job of run 35318628931 — the frozen-ref probe on ci/control-probe-2590, which has held a macOS runner for over eighty minutes. That is a scheduling queue, not a failure, and it is the same starvation this PR exists to make survivable.

Platform risk in this diff is as low as it gets: the change is a concurrency key in ci.yml plus one new test file that reads a YAML file and evaluates strings. Nothing in it branches on platform, and the identical suite already passed on all four Linux shards.

@lidge-jun
lidge-jun merged commit 1a135f1 into dev Sep 18, 2026
28 of 29 checks passed
@lidge-jun
lidge-jun deleted the codex/5037-dispatch-concurrency-group branch September 18, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant