From df052f10a7934dc4fc6c04a272e8e5437c8adc96 Mon Sep 17 00:00:00 2001 From: ArchonVII Date: Tue, 9 Jun 2026 17:57:49 -0500 Subject: [PATCH] fix(required-gate): isolate skipped label concurrency --- .changelog/unreleased/61-label-skip-concurrency.md | 4 ++++ README.md | 9 +++++---- examples/repo-required-gate.yml | 12 ++++++++++-- scripts/workflow-structure.test.mjs | 3 +++ 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 .changelog/unreleased/61-label-skip-concurrency.md diff --git a/.changelog/unreleased/61-label-skip-concurrency.md b/.changelog/unreleased/61-label-skip-concurrency.md new file mode 100644 index 0000000..3cf8f24 --- /dev/null +++ b/.changelog/unreleased/61-label-skip-concurrency.md @@ -0,0 +1,4 @@ +### Fixed + +- Isolated skipped non-`ci:full` label events into a separate required-gate + caller concurrency group so they cannot replace pending real gate runs. (#61) diff --git a/README.md b/README.md index 66f44b3..d589911 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,11 @@ The required-gate caller listens for `labeled` and `unlabeled` events only to preserve the `ci:full` escape hatch. The caller job and concurrency expression are guarded so `ci:full` is the only label change that can run or cancel the required branch-protection gate. Other labels such as `no-changelog` may create -a skipped caller run, but they do not start `repo-required-gate / decision` and -do not cancel an in-flight required-gate run. If a repo overrides the -`force-full-ci-label` input, update the caller guard and concurrency label name -to match. +a skipped caller run in a separate `label-skip-*` concurrency group, but they do +not start `repo-required-gate / decision`, cancel an in-flight required-gate +run, or replace a pending gate run in the real required-gate concurrency group. +If a repo overrides the `force-full-ci-label` input, update the caller guard and +concurrency label name to match. For agent closeout, use the shared local preflight instead of direct promotion: diff --git a/examples/repo-required-gate.yml b/examples/repo-required-gate.yml index 80b5710..4d4bfc4 100644 --- a/examples/repo-required-gate.yml +++ b/examples/repo-required-gate.yml @@ -15,7 +15,14 @@ permissions: pull-requests: write concurrency: - group: repo-required-gate-${{ github.event.pull_request.number || github.ref }} + group: >- + repo-required-gate-${{ github.event.pull_request.number || github.ref }}-${{ + github.event_name == 'pull_request' && + (github.event.action == 'labeled' || github.event.action == 'unlabeled') && + github.event.label.name != 'ci:full' && + format('label-skip-{0}', github.event.label.name) || + 'gate' + }} cancel-in-progress: >- ${{ github.event_name == 'pull_request' && @@ -120,7 +127,8 @@ jobs: # routing and run the full language CI surface — e.g. a workflow-only # change that semantically affects CI. The caller guard above allows # only this label to run/cancel the gate on label changes; other labels - # such as `no-changelog` skip without replacing the required decision. + # such as `no-changelog` skip in a separate concurrency group without + # replacing or canceling the required decision. # If you override the label input, update the guard/concurrency label # name above to match. # diff --git a/scripts/workflow-structure.test.mjs b/scripts/workflow-structure.test.mjs index 91c30bf..992a9cd 100644 --- a/scripts/workflow-structure.test.mjs +++ b/scripts/workflow-structure.test.mjs @@ -130,6 +130,9 @@ describe('repo-required-gate caller example', () => { expect(jobBlock).toContain("github.event.label.name == 'ci:full'"); const concurrencyBlock = body.slice(body.indexOf('concurrency:'), body.indexOf('jobs:')); + expect(concurrencyBlock).toContain('group: >-'); + expect(concurrencyBlock).toContain("format('label-skip-{0}', github.event.label.name)"); + expect(concurrencyBlock).toContain("'gate'"); expect(concurrencyBlock).toContain('cancel-in-progress: >-'); expect(concurrencyBlock).toContain("github.event.label.name == 'ci:full'"); });