Skip to content

Commit dcde03a

Browse files
committed
Skip automatic docs previews for fork PRs and drop the setup-uv retry steps
actions/checkout v7 refuses to fetch a fork's head in a pull_request_target run unless the step opts in with allow-unsafe-pr-checkout. Rather than opt in, have `authorize` skip fork heads on that path so the run ends cleanly instead of failing at checkout and posting a "Preview build failed" comment. Maintainers request a fork preview with /preview-docs, which runs under issue_comment and is unaffected; same-repo branches keep their automatic previews. setup-uv v10.0.1 retries the version-manifest fetch itself, which is what the continue-on-error + retry step pairs in shared.yml were waiting for, so collapse them to a single step. Also refresh comments the actions bump made stale (github-script runtime, default-branch context, read-only cache scope for these triggers).
1 parent 2e0a181 commit dcde03a

2 files changed

Lines changed: 39 additions & 69 deletions

File tree

.github/workflows/docs-preview.yml

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ name: Docs Preview
44
#
55
# Security: the build executes Python from the PR (mkdocstrings imports
66
# src/mcp, `!!python/name:` config directives run, and heads may ship their
7-
# own build scripts). The build is gated by `authorize` (admin sender for
8-
# auto-preview, admin/maintainer commenter for /preview-docs) and isolated
9-
# from Cloudflare secrets — `build` runs PR code with no secrets and hands
10-
# the static site to `deploy` via an artifact, so PR code never shares a
11-
# runner with the Cloudflare token.
7+
# own build scripts). The build is gated by `authorize` (admin sender on a
8+
# same-repo branch for auto-preview, admin/maintainer commenter for
9+
# /preview-docs) and isolated from Cloudflare secrets — `build` runs PR code
10+
# with no secrets and hands the static site to `deploy` via an artifact, so
11+
# PR code never shares a runner with the Cloudflare token. Fork PRs get no
12+
# automatic preview: actions/checkout refuses to fetch a fork's head in a
13+
# pull_request_target run, so a maintainer requests one with /preview-docs,
14+
# which runs under issue_comment with the same gating and isolation.
1215
#
1316
# Required configuration:
1417
# - secrets.CLOUDFLARE_API_TOKEN (scope: Account → Cloudflare Pages → Edit)
@@ -76,15 +79,25 @@ jobs:
7679
let slashAttempt = false;
7780
7881
if (context.eventName === 'pull_request_target') {
79-
// Gate on the *sender* (whoever caused this run — on synchronize that
80-
// is the pusher), not the PR author, so a non-admin pushing to an
81-
// admin-opened branch does not get an automatic build.
82-
const actor = context.payload.sender.login;
83-
prNumber = String(context.payload.pull_request.number);
84-
headSha = context.payload.pull_request.head.sha;
85-
const perm = await permissionFor(actor);
86-
authorized = perm.level === 'admin';
87-
core.info(`pull_request_target by ${actor} (level=${perm.level}, role=${perm.role}) → authorized=${authorized}`);
82+
const pr = context.payload.pull_request;
83+
prNumber = String(pr.number);
84+
headSha = pr.head.sha;
85+
// No automatic preview for fork PRs: actions/checkout refuses to fetch
86+
// a fork's head in a pull_request_target run. A maintainer can still
87+
// request one with /preview-docs. (head.repo is null once the fork is
88+
// deleted.)
89+
const headRepo = pr.head.repo;
90+
if (!headRepo || headRepo.id !== context.payload.repository.id) {
91+
core.info(`PR #${prNumber} head is on ${headRepo ? headRepo.full_name : 'a deleted fork'}; fork PRs are previewed via /preview-docs only.`);
92+
} else {
93+
// Gate on the *sender* (whoever caused this run — on synchronize that
94+
// is the pusher), not the PR author, so a non-admin pushing to an
95+
// admin-opened branch does not get an automatic build.
96+
const actor = context.payload.sender.login;
97+
const perm = await permissionFor(actor);
98+
authorized = perm.level === 'admin';
99+
core.info(`pull_request_target by ${actor} (level=${perm.level}, role=${perm.role}) → authorized=${authorized}`);
100+
}
88101
} else {
89102
// issue_comment: the job-level `if:` already guarantees this is a PR
90103
// comment starting with /preview-docs.
@@ -125,19 +138,20 @@ jobs:
125138
- name: Install uv
126139
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
127140
with:
128-
# pull_request_target runs share the base-branch Actions cache; saving
129-
# a cache populated while untrusted PR code ran would let it poison
130-
# later trusted workflows. Mirrors publish-pypi.yml.
141+
# Keep the untrusted build away from the shared Actions cache. GitHub
142+
# already limits pull_request_target and issue_comment runs to
143+
# read-only access in the default branch's cache scope, so this is
144+
# defence in depth (and avoids a refused save in the post step).
145+
# Mirrors publish-pypi.yml.
131146
enable-cache: false
132147
version: 0.9.5
133148

134-
# pull_request_target runs this workflow file from the base branch, so
135-
# the whole recipe — dependency sync included — must come from the
136-
# checkout itself: heads that ship scripts/docs/build.sh (the Zensical
137-
# toolchain) build with it; older heads, and v1.x heads previewed via
138-
# /preview-docs, still build with MkDocs. Both arms must write the site
139-
# to site/. Keep the detection in sync with build_site() in
140-
# scripts/build-docs.sh.
149+
# Both triggers run this workflow file from the default branch (whatever
150+
# the PR targets), so the whole recipe — dependency sync included — must
151+
# come from the checkout itself: heads that ship scripts/docs/build.sh
152+
# (the Zensical toolchain) build with it; older heads and v1.x heads still
153+
# build with MkDocs. Both arms must write the site to site/. Keep the
154+
# detection in sync with build_site() in scripts/build-docs.sh.
141155
- run: |
142156
if [ -f scripts/docs/build.sh ]; then
143157
bash scripts/docs/build.sh

.github/workflows/shared.yml

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,7 @@ jobs:
1717
with:
1818
persist-credentials: false
1919

20-
# setup-uv's manifest fetch is a single request with a hard 5s timeout
21-
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
2220
- name: Install uv
23-
id: setup-uv
24-
continue-on-error: true
25-
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
26-
with:
27-
enable-cache: true
28-
version: 0.9.5
29-
30-
- name: Install uv (retry)
31-
if: steps.setup-uv.outcome == 'failure'
3221
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
3322
with:
3423
enable-cache: true
@@ -43,7 +32,7 @@ jobs:
4332

4433
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
4534
with:
46-
node-version: 24 # match the runtime actions/github-script@v8 uses
35+
node-version: 24 # match actions/github-script's node24 runtime
4736
- name: PR intake gate scenarios
4837
run: node --test .github/scripts/pr_intake_gate.test.js
4938

@@ -79,18 +68,7 @@ jobs:
7968
with:
8069
persist-credentials: false
8170

82-
# setup-uv's manifest fetch is a single request with a hard 5s timeout
83-
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
8471
- name: Install uv
85-
id: setup-uv
86-
continue-on-error: true
87-
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
88-
with:
89-
enable-cache: true
90-
version: 0.9.5
91-
92-
- name: Install uv (retry)
93-
if: steps.setup-uv.outcome == 'failure'
9472
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
9573
with:
9674
enable-cache: true
@@ -125,18 +103,7 @@ jobs:
125103
with:
126104
persist-credentials: false
127105

128-
# setup-uv's manifest fetch is a single request with a hard 5s timeout
129-
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
130106
- name: Install uv
131-
id: setup-uv
132-
continue-on-error: true
133-
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
134-
with:
135-
enable-cache: true
136-
version: 0.9.5
137-
138-
- name: Install uv (retry)
139-
if: steps.setup-uv.outcome == 'failure'
140107
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
141108
with:
142109
enable-cache: true
@@ -165,18 +132,7 @@ jobs:
165132
with:
166133
persist-credentials: false
167134

168-
# setup-uv's manifest fetch is a single request with a hard 5s timeout
169-
# (astral-sh/setup-uv#869); retry once. Drop when upstream adds a retry.
170135
- name: Install uv
171-
id: setup-uv
172-
continue-on-error: true
173-
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
174-
with:
175-
enable-cache: true
176-
version: 0.9.5
177-
178-
- name: Install uv (retry)
179-
if: steps.setup-uv.outcome == 'failure'
180136
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
181137
with:
182138
enable-cache: true

0 commit comments

Comments
 (0)