From c9331b561ae019defed5fc15d00760445966c9c1 Mon Sep 17 00:00:00 2001 From: Jiangzhou He Date: Sat, 24 Oct 2026 12:23:28 -0700 Subject: [PATCH 1/2] ci-mode: fail the merge group of a pull request labelled merge-commit The queue squashes, so a pull request that must land as a merge commit, such as an upstream merge, bypasses it (CI.md, Merge commits). Nothing enforced that: cocoindex-plus#549 and #557, both OSS merges, went through the queue and lost their upstream parents (cocoindex-plus#561). On a merge_group event ci-mode now reads the labels of the pull request that the queue's branch names (gh-readonly-queue//pr--) and fails when it has `merge-commit`. ci-mode runs in the `changes` job that every other job needs, so the failure comes before any test runs, and ci-gate fails with it, which removes the pull request from the queue. A failed lookup fails too. Reading labels needs `pull-requests: read`, which the starter workflow already grants; only merge groups use it, so repositories without a queue are unaffected. Co-Authored-By: Claude Opus 5.5 --- CI.md | 10 +++++++++- actions/ci-mode/action.yml | 28 ++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/CI.md b/CI.md index cc90a6a..65433b2 100644 --- a/CI.md +++ b/CI.md @@ -48,7 +48,8 @@ workflow has all of them. 3. **Drafts skip.** Every job has `github.event.pull_request.draft != true` in its `if`, or needs a job that has it. 4. **A `changes` job** runs `cocoindex-io/.github/actions/ci-mode@v1` (it - needs `checks: read`) and the path filter. Expensive jobs run when + needs `checks: read`, and `pull-requests: read` in a repository with a + merge queue) and the path filter. Expensive jobs run when `full == 'true'` and the filter output is not `'false'`. The filter is skipped for a stack layer, and so is any other diff against the pull request's base branch: a stack layer's base is the layer below it, but @@ -110,6 +111,13 @@ requests directly. Run the full tier on its branch first with `gh pr merge --merge --admin`. Apart from an incident (below), nothing else bypasses the queue. +Label such a pull request `merge-commit` when you open it. `ci-mode` fails +the merge group of a pull request with that label before any test runs, so +the queue removes it instead of squashing it. A merge group whose label +lookup fails fails too: queueing a pull request again costs one build, and a +squashed merge commit costs a repair merge. The label has to be on the pull +request before it enters the queue. + ## Urgent changes An urgent fix jumps the queue: it goes to the front, is built next on top of diff --git a/actions/ci-mode/action.yml b/actions/ci-mode/action.yml index 2a4692d..f5c852d 100644 --- a/actions/ci-mode/action.yml +++ b/actions/ci-mode/action.yml @@ -1,8 +1,10 @@ name: CI mode description: >- - Decides whether this run executes the full CI tier or only the fast one. + Decides whether this run executes the full CI tier or only the fast one, + and fails a merge group whose pull request must land as a merge commit. See CI.md in cocoindex-io/.github for the tiers. The calling job needs - `checks: read` and `contents: read`. + `checks: read` and `contents: read`, and in a repository with a merge + queue also `pull-requests: read`. outputs: full: description: "'true' when this run executes the full tier, 'false' for the fast tier." @@ -22,6 +24,7 @@ runs: BASE: ${{ github.event.pull_request.stack.base.ref || github.event.pull_request.base.ref }} STACK_POSITION: ${{ github.event.pull_request.stack.position }} STACK_SIZE: ${{ github.event.pull_request.stack.size }} + HEAD_REF: ${{ github.event.merge_group.head_ref }} run: | full=true reason="a $EVENT run executes the full tier" @@ -50,6 +53,27 @@ runs: else echo "::warning::Could not read the checks of $SHA, so this run executes the full tier." fi + elif [ "$EVENT" = merge_group ]; then + # The queue squashes. A pull request labelled merge-commit, such as + # an upstream merge, must keep its merge commit, so its merge group + # fails here, before any expensive job starts, and the queue removes + # it. A failed lookup fails too: queueing a pull request again costs + # one build, and a squashed merge commit costs a repair. The queue's + # branch names the pull request: gh-readonly-queue//pr--. + pr=${HEAD_REF##*/pr-} + pr=${pr%%-*} + if ! [[ $pr =~ ^[0-9]+$ ]]; then + echo "::error::Could not find the pull request number in the merge group ref $HEAD_REF." + exit 1 + fi + if ! labelled=$(gh api "repos/$REPO/pulls/$pr" --jq 'any(.labels[]; .name == "merge-commit")'); then + echo "::error::Could not read the labels of #$pr. The job that runs ci-mode needs \`pull-requests: read\` in a repository with a merge queue." + exit 1 + fi + if [ "$labelled" = true ]; then + echo "::error::#$pr is labelled merge-commit, so it must land as a merge commit, and the queue squashes. Merge it around the queue after a full run on its branch passes: gh pr merge $pr --merge --admin (CI.md, Merge commits)." + exit 1 + fi fi echo "full=$full" >> "$GITHUB_OUTPUT" tier=fast From c48276cf3a4c505c8ef7513c35c039728b94f291 Mon Sep 17 00:00:00 2001 From: Jiangzhou He Date: Sat, 24 Oct 2026 12:24:08 -0700 Subject: [PATCH 2/2] ci-mode: the lookup-failure message names both causes Co-Authored-By: Claude Opus 5.5 --- CI.md | 8 ++++---- actions/ci-mode/action.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CI.md b/CI.md index 65433b2..1dce3f9 100644 --- a/CI.md +++ b/CI.md @@ -113,10 +113,10 @@ bypasses the queue. Label such a pull request `merge-commit` when you open it. `ci-mode` fails the merge group of a pull request with that label before any test runs, so -the queue removes it instead of squashing it. A merge group whose label -lookup fails fails too: queueing a pull request again costs one build, and a -squashed merge commit costs a repair merge. The label has to be on the pull -request before it enters the queue. +the queue removes it instead of squashing it. When `ci-mode` can't read the +labels, the merge group fails too: queueing a pull request again costs one +build, and a squashed merge commit costs a repair merge. The label has to be +on the pull request before it enters the queue. ## Urgent changes diff --git a/actions/ci-mode/action.yml b/actions/ci-mode/action.yml index f5c852d..6c1299e 100644 --- a/actions/ci-mode/action.yml +++ b/actions/ci-mode/action.yml @@ -67,7 +67,7 @@ runs: exit 1 fi if ! labelled=$(gh api "repos/$REPO/pulls/$pr" --jq 'any(.labels[]; .name == "merge-commit")'); then - echo "::error::Could not read the labels of #$pr. The job that runs ci-mode needs \`pull-requests: read\` in a repository with a merge queue." + echo "::error::Could not read the labels of #$pr, so its merge group fails. Queue it again; if this repeats, check that the job that runs ci-mode has \`pull-requests: read\`." exit 1 fi if [ "$labelled" = true ]; then