Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit e1977d1

Browse files
authored
ci(trunk): upload impacted targets with fork-aware auth so fork PRs can enter the merge queue (#3429)
1 parent 0a6820e commit e1977d1

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Uploads this PR's impacted targets to Trunk so the parallel merge queue can
2+
# schedule it. Trunk needs an impacted-targets upload to place a PR into a lane;
3+
# without one, fork PRs never enter the queue and have to be merged by hand (see
4+
# PR #2819).
5+
#
6+
# The upload authenticates with the org API token (x-api-token) on internal PRs.
7+
# Fork PR workflow runs do NOT receive repo secrets — GitHub withholds them from
8+
# `pull_request` runs originating in a fork — so for forks we authenticate with
9+
# the workflow run id instead (x-forked-workflow-run-id). Trunk verifies the run
10+
# id belongs to a live fork-PR workflow whose head SHA matches the payload.
11+
# Ref: https://docs.trunk.io/merge-queue/optimizations/parallel-queues/api#handling-forked-pull-requests
12+
#
13+
# SECURITY: this MUST stay on the `pull_request` trigger, never
14+
# `pull_request_target`. `pull_request_target` would hand repo secrets to code
15+
# from untrusted forks ("pwn request"). The fork path deliberately needs no
16+
# secret, so the plain `pull_request` trigger is sufficient and safe.
17+
name: Trunk Impacted Targets
18+
19+
on:
20+
pull_request:
21+
types: [opened, synchronize, reopened, ready_for_review]
22+
23+
concurrency:
24+
# Supersede in-flight uploads when the PR head moves; the last upload per head
25+
# SHA wins on Trunk's side anyway. Key on the PR number, not head_ref: fork
26+
# PRs frequently share a source branch name (many are opened from `main`), and
27+
# keying on head_ref would put two such PRs in one group where one cancels the
28+
# other's upload — leaving that head SHA without the upload it needs to enter
29+
# the queue.
30+
group: trunk-impacted-targets-${{ github.event.pull_request.number || github.head_ref || github.ref }}
31+
cancel-in-progress: true
32+
33+
jobs:
34+
upload:
35+
runs-on: ubuntu-latest
36+
# Reads only the event payload and talks out to Trunk — no repo write or PR
37+
# API access needed.
38+
permissions:
39+
contents: read
40+
env:
41+
IS_FORK: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
42+
RUN_ID: ${{ github.run_id }}
43+
REPO_OWNER: ${{ github.repository_owner }}
44+
REPO_NAME: ${{ github.event.repository.name }}
45+
PR_NUMBER: ${{ github.event.pull_request.number }}
46+
# Head SHA (not the synthetic merge commit): it matches the workflow run's
47+
# head_sha, which is what Trunk checks when verifying a fork upload.
48+
PR_SHA: ${{ github.event.pull_request.head.sha }}
49+
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
50+
TRUNK_API_TOKEN: ${{ secrets.TRUNK_API_TOKEN }}
51+
steps:
52+
- name: Upload impacted targets to Trunk
53+
# Not continue-on-error: a silent failure here is exactly what kept fork
54+
# PRs out of the queue, so surface upload problems loudly. --retry rides
55+
# out transient Trunk/network blips.
56+
run: |
57+
set -euo pipefail
58+
59+
# We report "ALL" — every target — which is always correct: it can
60+
# never under-report and let the queue merge conflicting PRs in
61+
# parallel. It yields no parallelism benefit, but it gets every PR
62+
# (forks included) into the queue. To actually parallelise, replace
63+
# "ALL" with a computed target-name list (e.g. derived from the
64+
# dorny/paths-filter outputs in test.yml) once the queue's target
65+
# names are defined; see the Trunk bazel-action reference impl.
66+
payload="$(jq -n \
67+
--arg host "github.com" \
68+
--arg owner "$REPO_OWNER" \
69+
--arg name "$REPO_NAME" \
70+
--argjson number "$PR_NUMBER" \
71+
--arg sha "$PR_SHA" \
72+
--arg targetBranch "$TARGET_BRANCH" \
73+
'{repo: {host: $host, owner: $owner, name: $name}, pr: {number: $number, sha: $sha}, targetBranch: $targetBranch, impactedTargets: "ALL"}')"
74+
75+
if [ "$IS_FORK" = "true" ]; then
76+
echo "Fork PR — authenticating with x-forked-workflow-run-id ($RUN_ID)"
77+
auth_header="x-forked-workflow-run-id: $RUN_ID"
78+
else
79+
echo "Internal PR — authenticating with x-api-token"
80+
auth_header="x-api-token: $TRUNK_API_TOKEN"
81+
fi
82+
83+
curl --fail --silent --show-error --retry 3 --retry-all-errors \
84+
-X POST "https://api.trunk.io/v1/setImpactedTargets" \
85+
-H "Content-Type: application/json" \
86+
-H "$auth_header" \
87+
--data "$payload"

0 commit comments

Comments
 (0)