Skip to content

ci: refuse merge-queue runs that contain fork or Dependabot pull requests - #165

Merged
oschaaf merged 5 commits into
masterfrom
ci/queue-fork-guard
Oct 22, 2026
Merged

oschaaf merged 5 commits into
masterfrom
ci/queue-fork-guard

Conversation

@oschaaf

@oschaaf oschaaf commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

What

A merge-queue run builds the queued commits on the fleet runners, and the pull_request event is the only place fork and Dependabot pull requests are routed to hosted labels. This adds a hosted refusal for merge_group runs.

The changes job (hosted) gets a first step, Refuse fork and Dependabot PRs in the merge queue, that runs only on merge_group. It resolves every pull request in the group (the last (#N) of each commit subject in base_sha..head_sha, plus the number in head_ref), looks each one up with gh api (5 tries, 2/4/8/16 s backoff) and fails closed:

  • the pull request is from a fork, is authored by dependabot[bot], or its head repository is gone or is not this repository;
  • the range is empty, a commit subject has no (#N), or the head_ref cannot be parsed;
  • a lookup still fails after the last try.

vendor now needs changes, and every other fleet job already needs vendor or changes, so a refusal skips the whole fleet and CI gate goes red. The changes job gets pull-requests: read.

The classifier is tools/ci/queue_fork_guard.sh. The step reads it from the default branch (git show "origin/${DEFAULT_BRANCH}:..." | bash), never from the queued tree, with set -euo pipefail so a missing script fails the step. This pull request's own merge-group run uses the pipeline already on the default branch (the caller pins it there), so no bootstrap path is needed.

The workflow-shape gate check queue-guard-reads-default-branch requires the step as the first step after the checkout in the changes job, fixes its keys, if:, env and run: block, rejects any other spelling that reads the script (truncated, globbed, braced, find, source) and env that could redirect it (BASH_ENV, PATH, GIT_*), and requires vendor to have needs: changes and no if:; each rejection has a fixture.

Assumption

The PR-number rule relies on the queue's squash merge method (one (#N) commit per pull request). A different method must change the classifier first.

Scope

The guard protects runs that go through pipeline-pr.yml. A queued tree can still change the caller ci.yml to call another allow-listed fleet pipeline (full, nightly, release and the other reusable workflows, and the @ci-dev refs), and those carry no guard. Closing that is a separate change; until it lands, no fork or Dependabot pull request is added to the merge queue (policy).

Testing

  • tools/ci/test_queue_fork_guard.sh (fake gh and sleep, temporary git repository with squash-style commits): same-repo group of three, fork in the middle, Dependabot, foreign head repository, deleted head repository, no PR number, unparseable head ref, API 500 (exactly 5 calls, backoff 2/4/8/16), empty range, PR number only in head_ref (looked up once). Registered in CI.
  • tools/ci/test_check_workflow_shape.sh covers the new check with one fixture per rejection and the accepted form.
  • tools/ci/check-workflow-shape.sh, actionlint, shellcheck and the public-tree hygiene gate pass.

@oschaaf
oschaaf enabled auto-merge October 22, 2026 19:18
@oschaaf
oschaaf added this pull request to the merge queue Oct 22, 2026
Merged via the queue into master with commit 43071d3 Oct 22, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant