From ecce5c00efe7c55992aaf361f2184fb88d0bf84e Mon Sep 17 00:00:00 2001 From: Mislav Ivanda Date: Thu, 17 Sep 2026 12:55:04 +0000 Subject: [PATCH] ci(integration): fix fork-lane checkout opt-in and per-event concurrency Three fixes hardening + unwedging fork-PR live tests (first hit by PR #61): 1. checkout@v4 now refuses fork-code checkout in pull_request_target workflows by default. Opt in explicitly on the five suite jobs - safe here because the integration-tests environment approval is a required manual review gate, the head SHA is pinned at event time, and the workflow file always comes from main. 2. Key the concurrency group by event name. Both pull_request and pull_request_target fire for every fork PR and shared one group, so the newer queued run cancelled the older one - always killing the fork lane, the only lane that reports integration-success for fork PRs. Exactly one lane executes suites per PR, so per-lane serialization loses nothing. 3. persist-credentials: false on the suite checkouts - the approved fork code has no need for the (read-only) GITHUB_TOKEN that checkout would otherwise leave in .git/config. Signed-off-by: Mislav Ivanda --- .github/workflows/integration.yml | 58 ++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 03d0fd0..3d2b68e 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -44,8 +44,14 @@ permissions: # sandboxes). Serialize per PR instead (ref as the dispatch fallback): under # `pull_request_target`, `github.ref` is the BASE branch, which would collapse # every fork-lane run repo-wide into a single queue. +# The event name is part of the key because BOTH `pull_request` and +# `pull_request_target` fire for every fork PR: in a shared queue the newer +# run cancels the older waiting one ("higher priority waiting request"), which +# kills the fork lane — the only lane that reports integration-success for +# forks. Exactly one lane ever executes suites for a given PR (see decide()), +# so per-lane serialization loses nothing. concurrency: - group: integration-${{ github.event.pull_request.number || github.ref }} + group: integration-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: false jobs: @@ -134,6 +140,16 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} + # Deliberate opt-in to fork-code checkout in the pull_request_target + # lane (checkout@v4 refuses it by default). Safe HERE only because the + # `integration-tests` environment approval is a required manual review + # gate in front of this job, the SHA is pinned at event time, and the + # workflow file always comes from main. Do not copy this flag into + # jobs that lack those controls. + allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }} + # Don't leave GITHUB_TOKEN in .git/config where the (approved) fork + # code could read it — the suites never push/pull after checkout. + persist-credentials: false - uses: actions/setup-python@v5 with: python-version: "3.x" @@ -157,6 +173,16 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} + # Deliberate opt-in to fork-code checkout in the pull_request_target + # lane (checkout@v4 refuses it by default). Safe HERE only because the + # `integration-tests` environment approval is a required manual review + # gate in front of this job, the SHA is pinned at event time, and the + # workflow file always comes from main. Do not copy this flag into + # jobs that lack those controls. + allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }} + # Don't leave GITHUB_TOKEN in .git/config where the (approved) fork + # code could read it — the suites never push/pull after checkout. + persist-credentials: false - uses: actions/setup-python@v5 with: # Pinned to 3.12 for the same reason as ci.yml (obstore wheels). @@ -183,6 +209,16 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} + # Deliberate opt-in to fork-code checkout in the pull_request_target + # lane (checkout@v4 refuses it by default). Safe HERE only because the + # `integration-tests` environment approval is a required manual review + # gate in front of this job, the SHA is pinned at event time, and the + # workflow file always comes from main. Do not copy this flag into + # jobs that lack those controls. + allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }} + # Don't leave GITHUB_TOKEN in .git/config where the (approved) fork + # code could read it — the suites never push/pull after checkout. + persist-credentials: false - uses: actions/setup-node@v4 with: node-version: "lts/*" @@ -212,6 +248,16 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} + # Deliberate opt-in to fork-code checkout in the pull_request_target + # lane (checkout@v4 refuses it by default). Safe HERE only because the + # `integration-tests` environment approval is a required manual review + # gate in front of this job, the SHA is pinned at event time, and the + # workflow file always comes from main. Do not copy this flag into + # jobs that lack those controls. + allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }} + # Don't leave GITHUB_TOKEN in .git/config where the (approved) fork + # code could read it — the suites never push/pull after checkout. + persist-credentials: false - uses: actions/setup-node@v4 with: node-version: "lts/*" @@ -235,6 +281,16 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} + # Deliberate opt-in to fork-code checkout in the pull_request_target + # lane (checkout@v4 refuses it by default). Safe HERE only because the + # `integration-tests` environment approval is a required manual review + # gate in front of this job, the SHA is pinned at event time, and the + # workflow file always comes from main. Do not copy this flag into + # jobs that lack those controls. + allow-unsafe-pr-checkout: ${{ github.event_name == 'pull_request_target' }} + # Don't leave GITHUB_TOKEN in .git/config where the (approved) fork + # code could read it — the suites never push/pull after checkout. + persist-credentials: false - uses: actions/setup-python@v5 with: # Pinned to 3.12 for the same reason as ci.yml (obstore wheels).