From ab4131dfc32e420096d788beca963e1f85846ad0 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 14 Jul 2026 09:43:54 -0400 Subject: [PATCH 1/2] ci(trunk): upload impacted targets with fork-aware auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fork PRs never entered the Trunk parallel merge queue and had to be merged by hand (e.g. #2819). Trunk needs an impacted-targets upload to place a PR into a lane, and the upload authenticates with the org API token — which GitHub withholds from fork `pull_request` runs. Add a dedicated workflow that uploads to /v1/setImpactedTargets on every PR, using x-api-token on internal PRs and x-forked-workflow-run-id (from github.run_id) on fork PRs, per Trunk's guidance. Reports "ALL" targets for correctness; refining to a computed target list is left as a documented TODO. Stays on the `pull_request` trigger (never pull_request_target) so no secret is ever exposed to fork code. Generated-By: PostHog Code Task-Id: 295c0ed1-48f3-4bd4-9d0e-dba8d17031ca --- .github/workflows/trunk-impacted-targets.yml | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/trunk-impacted-targets.yml diff --git a/.github/workflows/trunk-impacted-targets.yml b/.github/workflows/trunk-impacted-targets.yml new file mode 100644 index 0000000000..c7bac1d999 --- /dev/null +++ b/.github/workflows/trunk-impacted-targets.yml @@ -0,0 +1,83 @@ +# Uploads this PR's impacted targets to Trunk so the parallel merge queue can +# schedule it. Trunk needs an impacted-targets upload to place a PR into a lane; +# without one, fork PRs never enter the queue and have to be merged by hand (see +# PR #2819). +# +# The upload authenticates with the org API token (x-api-token) on internal PRs. +# Fork PR workflow runs do NOT receive repo secrets — GitHub withholds them from +# `pull_request` runs originating in a fork — so for forks we authenticate with +# the workflow run id instead (x-forked-workflow-run-id). Trunk verifies the run +# id belongs to a live fork-PR workflow whose head SHA matches the payload. +# Ref: https://docs.trunk.io/merge-queue/optimizations/parallel-queues/api#handling-forked-pull-requests +# +# SECURITY: this MUST stay on the `pull_request` trigger, never +# `pull_request_target`. `pull_request_target` would hand repo secrets to code +# from untrusted forks ("pwn request"). The fork path deliberately needs no +# secret, so the plain `pull_request` trigger is sufficient and safe. +name: Trunk Impacted Targets + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +concurrency: + # Supersede in-flight uploads when the PR head moves; the last upload per head + # SHA wins on Trunk's side anyway. + group: trunk-impacted-targets-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + upload: + runs-on: ubuntu-latest + # Reads only the event payload and talks out to Trunk — no repo write or PR + # API access needed. + permissions: + contents: read + env: + IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + RUN_ID: ${{ github.run_id }} + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + PR_NUMBER: ${{ github.event.pull_request.number }} + # Head SHA (not the synthetic merge commit): it matches the workflow run's + # head_sha, which is what Trunk checks when verifying a fork upload. + PR_SHA: ${{ github.event.pull_request.head.sha }} + TARGET_BRANCH: ${{ github.event.pull_request.base.ref }} + TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }} + steps: + - name: Upload impacted targets to Trunk + # Not continue-on-error: a silent failure here is exactly what kept fork + # PRs out of the queue, so surface upload problems loudly. --retry rides + # out transient Trunk/network blips. + run: | + set -euo pipefail + + # We report "ALL" — every target — which is always correct: it can + # never under-report and let the queue merge conflicting PRs in + # parallel. It yields no parallelism benefit, but it gets every PR + # (forks included) into the queue. To actually parallelise, replace + # "ALL" with a computed target-name list (e.g. derived from the + # dorny/paths-filter outputs in test.yml) once the queue's target + # names are defined; see the Trunk bazel-action reference impl. + payload="$(jq -n \ + --arg host "github.com" \ + --arg owner "$REPO_OWNER" \ + --arg name "$REPO_NAME" \ + --argjson number "$PR_NUMBER" \ + --arg sha "$PR_SHA" \ + --arg targetBranch "$TARGET_BRANCH" \ + '{repo: {host: $host, owner: $owner, name: $name}, pr: {number: $number, sha: $sha}, targetBranch: $targetBranch, impactedTargets: "ALL"}')" + + if [ "$IS_FORK" = "true" ]; then + echo "Fork PR — authenticating with x-forked-workflow-run-id ($RUN_ID)" + auth_header="x-forked-workflow-run-id: $RUN_ID" + else + echo "Internal PR — authenticating with x-api-token" + auth_header="x-api-token: $TRUNK_API_TOKEN" + fi + + curl --fail --silent --show-error --retry 3 --retry-all-errors \ + -X POST "https://api.trunk.io/v1/setImpactedTargets" \ + -H "Content-Type: application/json" \ + -H "$auth_header" \ + --data "$payload" From f28a0924c78882a55a0144043a2c76231b8957b9 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Tue, 14 Jul 2026 12:32:38 -0400 Subject: [PATCH 2/2] ci(trunk): key impacted-targets concurrency on PR number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fork PRs frequently share a source branch name (many are opened from `main`), so keying the concurrency group on github.head_ref would put two such PRs in one group where cancel-in-progress lets one cancel the other's upload — leaving that head SHA without the impacted-targets upload it needs to enter the queue. Key on github.event.pull_request.number instead. Generated-By: PostHog Code Task-Id: 295c0ed1-48f3-4bd4-9d0e-dba8d17031ca --- .github/workflows/trunk-impacted-targets.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/trunk-impacted-targets.yml b/.github/workflows/trunk-impacted-targets.yml index c7bac1d999..f6e7009b93 100644 --- a/.github/workflows/trunk-impacted-targets.yml +++ b/.github/workflows/trunk-impacted-targets.yml @@ -22,8 +22,12 @@ on: concurrency: # Supersede in-flight uploads when the PR head moves; the last upload per head - # SHA wins on Trunk's side anyway. - group: trunk-impacted-targets-${{ github.head_ref || github.ref }} + # SHA wins on Trunk's side anyway. Key on the PR number, not head_ref: fork + # PRs frequently share a source branch name (many are opened from `main`), and + # keying on head_ref would put two such PRs in one group where one cancels the + # other's upload — leaving that head SHA without the upload it needs to enter + # the queue. + group: trunk-impacted-targets-${{ github.event.pull_request.number || github.head_ref || github.ref }} cancel-in-progress: true jobs: