Skip to content

ci: fail held merge-queue pull requests before the lane legs run #10747

Closed

Description

@kyle-sexton

Problem

About 20-26 merge-group runs a day fail on a designed hold (hold label, body or member hold, a pr-unblock stop on the head, pr-verify not passed, needs-human, kill switch, hold path). ci-status checks these holds only after every lane leg in the merge_group run has finished, which is about 32 job-minutes per run. The queue then ejects the pull request.

#10737 stops pr-merge's enable-auto-merge from queuing a head a pr-unblock lane stopped. It moves that rule into merge-holds.mjs as unblockStops and readStatuses. Two parts were left for after it merges.

Remaining

  1. ci-status calls the shared rule. The Fail a head a pr-unblock lane stopped step in pr-require-checks.yml still applies its own jq and awk rule. ci-status loads merge-holds.mjs from the base commit ($BASE_CONFIG), so it can import unblockStops only once fix(pr-pipeline): end designed stops cleanly and keep held pull requests out of the merge queue #10737 is on main. Switch the step to holds_js unblockStops and update scripts/ci-hold-pr-unblock-status.test.sh to match.
  2. Check holds early in merge_group runs. Run the hold checks that cost no lane work (hold labels, pr-unblock stops, kill switch) in a merge_group-only step of select-tests, which every leg needs. A held pull request then fails in seconds and its legs skip. ci-status keeps its own hold steps, so the final aggregate still fails on the hold. To avoid a second copy of each rule, both places call one script or composite action. Two things to check first: select-tests runs pull request code with contents: read and pull-requests: read only, so confirm a commit-status read works with those permissions. And make sure the legs skipping does not turn a skip into a pass in the Check each skipped lane against scope step.

Done when

  • A merge_group run for a held pull request fails before any lane leg starts.
  • Every state that fails ci-status today still fails it.
  • The pr-unblock rule is stated once, in merge-holds.mjs.

Related

Refs: #10737

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 28, 2026
  2. github-actions commented on Oct 28, 2026

    @github-actions
    Contributor

    The triage sweep dispatched intake triage for this issue (attempt 1 of 2).

  3. github-actions commented on Oct 28, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: scoped
    Basis: automated intake triage; the lanes App applied this class.

    Size: Small
    Complexity: Medium
    Uncertainty: Knowable
    Flags: none
    Blocked by: #10737

    I read this as two changes to pr-require-checks.yml. First, the ci-status pr-unblock step calls the shared unblockStops rule in merge-holds.mjs. Second, a merge_group-only select-tests step runs the no-lane-cost hold checks, so a held pull request fails before any lane leg starts. Both would call one shared script or composite action.

    Item 1 waits on #10737 merging, since unblockStops is not on main yet. To confirm first: whether a commit-status read works with select-tests' current permissions (or needs statuses: read), and that skipped legs cannot turn into a pass in Check each skipped lane against scope.

    No duplicate found among the open issues.

    Automated triage, applied by the lanes App.

  4. added
    priority: mediumReal value, no hard deadline; normal backlog flow.
    and removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 28, 2026
  5. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 28, 2026
  6. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane: a criterion names .github/workflows/pr-require-checks.yml, .github/workflows, which the issue lane cannot push (patch-protected-path); a builder session takes it. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).

  7. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    The latest triage verdict holds nothing: admission moved the issue to status: confirmed; its comment says why.

  8. melodic-automation-lanes commented on Oct 28, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.

    Intended outcome: ci-status's pr-unblock step calls the shared unblockStops rule in merge-holds.mjs, and a merge_group-only step in select-tests runs the no-lane-cost hold checks so a held pull request fails before any lane leg starts.

    Acceptance criteria

    • The step Fail a head a pr-unblock lane stopped in .github/workflows/pr-require-checks.yml calls holds_js unblockStops and holds no jq or awk copy of the pr-unblock rule.
    • scripts/ci-hold-pr-unblock-status.test.sh is updated to run the new step body and passes, including a failure case for a failure or error pr-unblock/* status on the head.
    • The pr-unblock stop rule is stated once, in merge-holds.mjs: grep finds no second implementation in .github/workflows or scripts.
    • select-tests has a step that runs only when github.event_name is merge_group and checks hold labels, pr-unblock stops and the kill switch.
    • Both select-tests and ci-status call the same single script or composite action for those checks, so no rule is copied.
    • The select-tests permissions block grants only what the status read needs (add statuses: read if the read fails without it), with no write scope, and a comment says why.
    • The Check each skipped lane against scope step still fails when legs skip because select-tests failed on a hold; a test or workflow-lint check pins this.
    • ci-status keeps its own hold steps, so every state that fails ci-status today still fails it; existing hold tests still pass.
    • A test or fixture shows a held merge_group head fails the select-tests step, and a head with no hold passes it.
    • The workflow-lint and shell test suites for the touched files pass.

    Dependencies

    • #​10737 must merge first: .base has no unblockStops in merge-holds.mjs yet, and ci-status loads merge-holds.mjs from the base commit, so item 1 cannot land until then. Item 2 can reuse that rule only after it too.

    Related work

    Sources read

    • /home/runner/work/claude-code-plugins/claude-code-plugins/refine-data/context.json: issue 10747 body, triage, and candidates; no maintainer decision comments.
    • .github/workflows/pr-require-checks.yml:5031: the Fail a head a pr-unblock lane stopped step still uses its own jq and awk rule over commit statuses.
    • .github/workflows/pr-require-checks.yml:4143: the holds_js helper exists, but a grep of .base finds no unblockStops anywhere.
    • .github/workflows/pr-require-checks.yml:287: select-tests sets permissions to contents: read and pull-requests: read only, so a commit-status read likely needs statuses: read added.
    • .github/workflows/pr-require-checks.yml:239: a comment says no job that runs pull request code holds statuses: write, so a read-only grant fits that rule.
    • .github/workflows/pr-require-checks.yml:5381: the Check each skipped lane against scope step is where a skip must not become a pass.
    • scripts/ci-hold-pr-unblock-status.test.sh:28: the test runs the pr-unblock step body by name.

    Automated refinement by the lanes App. Intake triages the issue again with it.

  9. removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 28, 2026
  10. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.
    and removed
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    on Oct 28, 2026
  11. kyle-sexton commented on Oct 29, 2026

    @kyle-sexton
    MemberAuthor

    Done on main (verified 2cc3903). (1) ci-status's 'Fail a head a pr-unblock lane stopped' step imports the base commit's merge-holds.mjs unblockStops (pr-require-checks.yml ~:5357), so the rule is stated once. (2) select-tests runs a merge_group-only step that reads the base commit's queueHolds (labelHolds + unblockStops + switchHold) and fails a held group before the lane legs (pr-require-checks.yml:346-396; #10838, #10876). ci-status keeps its own hold steps, so every state that failed it still does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: mediumReal value, no hard deadline; normal backlog flow.status: confirmedA maintainer confirmed the item is real and in scope; ready to be worked.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Fields

    No fields configured for Task.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions