Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/unreleased/57-required-gate-label-triggers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Stop the required-gate caller example from rerunning on arbitrary PR label changes. The `ci:full` classifier label still works when the gate runs, but labels such as `no-changelog` no longer restart the branch-protection gate.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ repo-required-gate / decision

Keep targeted checks inside the gate or leave them non-required. Do not make branch protection depend on workflows that can be skipped by path filters; GitHub can leave those required checks pending.

The required-gate caller intentionally does not listen for `labeled` or
`unlabeled` events. Labels such as `no-changelog` should not cancel and restart
the required branch-protection gate. The `ci:full` label is still honored by the
classifier when the gate runs; after applying it to an already-open PR, rerun the
workflow, push a new commit, or use another normal PR event to refresh the gate.

For agent closeout, use the shared local preflight instead of direct promotion:

```bash
Expand Down
13 changes: 3 additions & 10 deletions examples/repo-required-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,7 @@ name: Repo Required Gate
on:
pull_request:
branches: [main]
types:
[
opened,
edited,
synchronize,
reopened,
ready_for_review,
labeled,
unlabeled,
]
types: [opened, edited, synchronize, reopened, ready_for_review]
merge_group:

permissions:
Expand Down Expand Up @@ -115,5 +106,7 @@ jobs:
# Default label is `ci:full`. Apply it to a PR to bypass path-based
# routing and run the full language CI surface — e.g. a workflow-only
# change that semantically affects CI.
# If you add the label after the latest gate run, rerun the workflow,
# push a new commit, or use another normal PR event to refresh the gate.
Comment on lines +109 to +110

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fetch current labels before suggesting reruns

When ci:full is added after the latest gate run and a maintainer follows this new instruction to rerun, the reusable workflow still builds labels from context.payload.pull_request in .github/workflows/repo-required-gate.yml, which is the original PR event payload rather than a fresh label lookup. GitHub re-runs use the original event's SHA/ref (docs), so the new label is not reliably present and the forced-full lane will not run; only a new PR event such as a push/edit refreshes it. Either fetch current issue labels during classification or remove rerun the workflow from this workaround.

Useful? React with 👍 / 👎.

#
# force-full-ci-label: ci:full
11 changes: 11 additions & 0 deletions scripts/workflow-structure.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs';
import { describe, expect, it } from 'vitest';

const readWorkflow = (name) => readFileSync(`.github/workflows/${name}.yml`, 'utf8');
const readExample = (name) => readFileSync(`examples/${name}.yml`, 'utf8');

const workflowJobBlock = (body, job) => {
const marker = ` ${job}:`;
Expand Down Expand Up @@ -113,6 +114,16 @@ describe('repo-required-gate workflow node delegation', () => {
});
});

describe('repo-required-gate caller example', () => {
it('does not rerun the required gate for arbitrary label changes', () => {
const body = readExample('repo-required-gate');

expect(body).toContain('types: [opened, edited, synchronize, reopened, ready_for_review]');
expect(body).not.toMatch(/^\s*labeled,?\s*$/m);
expect(body).not.toMatch(/^\s*unlabeled,?\s*$/m);
});
});

describe('pr-policy workflow contract source', () => {
it('uses the shared PR contract validator instead of inline body regexes', () => {
const body = readWorkflow('pr-policy');
Expand Down
Loading