Skip to content

Commit fb7259b

Browse files
committed
ci: TEMPORARY — exercise the deploy's own inputs on a pull request
Reverted in the next commit on this branch. Pushed as its own commit so the evidence and its removal are both on the record. `Package the Worker` and `Upload the Worker bundle` are push-only, so `opennextjs-cloudflare build --skipNextBuild` has never once executed with an incremental cache configured — its first run would be the merge commit. If that path does not produce or does not preserve `.open-next/cache`, the Worker deploys with the cache CONFIGURED and EMPTY: every lookup misses, `dynamicParams = false` refuses the on-demand render, and every page 404s. That is the outage this branch diagnoses, reproduced by its own fix. So the two steps gain a `pull_request` clause — the only difference from what ships — and a temporary job downloads the artifact and runs `opennextjs-cloudflare populateCache local`, which is exactly what `opennextjs-cloudflare deploy` runs before `wrangler deploy` and, for the static-assets cache, a filesystem copy needing no credentials. It then weighs the bundle with `wrangler deploy --dry-run`, which gives #262 a CI-measured number instead of one scaled from a local ratio. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkauAsZBEemRbco2rEX9Lx
1 parent f35234f commit fb7259b

1 file changed

Lines changed: 115 additions & 2 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,55 @@ jobs:
150150
# Last in the job on purpose: the artifact then only exists for a commit
151151
# that cleared every gate above it. And only on a push to `main`, because
152152
# that is the only event that deploys, so a pull request pays nothing.
153+
# TEMPORARY (#261 round 3, reverted in this branch before review): the
154+
# `pull_request` clause below is the only difference from the shipped
155+
# step. `--skipNextBuild` has never once executed with the incremental
156+
# cache configured, because this step is push-only; its first run would
157+
# otherwise be the merge commit. If that path does not produce
158+
# `.open-next/cache`, the Worker deploys with the cache CONFIGURED and
159+
# EMPTY, every lookup misses, `dynamicParams = false` refuses the
160+
# on-demand render, and every page 404s — the outage this PR diagnoses,
161+
# reproduced by its own fix.
153162
- name: Package the Worker from the build this job tested
154-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
163+
if: >-
164+
(github.event_name == 'push' && github.ref == 'refs/heads/main')
165+
|| github.event_name == 'pull_request'
155166
working-directory: apps/docs
156167
run: pnpm exec opennextjs-cloudflare build --skipNextBuild
157168

169+
# TEMPORARY (#261 round 3, reverted with the clause above).
170+
- name: TEMP — the cache the deploy depends on, as CI's own build makes it
171+
if: github.event_name == 'pull_request'
172+
working-directory: apps/docs
173+
shell: bash
174+
run: |
175+
set -euo pipefail
176+
test -d .open-next/cache || { echo "::error::.open-next/cache absent after --skipNextBuild"; exit 1; }
177+
echo "cache entries : $(find .open-next/cache -type f | wc -l)"
178+
echo "cache bytes : $(du -sb .open-next/cache | cut -f1)"
179+
node -e '
180+
const fs=require("fs"),path=require("path");
181+
const bid=fs.readFileSync(".open-next/assets/BUILD_ID","utf8").trim();
182+
const m=JSON.parse(fs.readFileSync(".open-next/server-functions/default/apps/docs/.next/prerender-manifest.json","utf8"));
183+
const routes=Object.keys(m.routes||{});
184+
const root=path.join(".open-next/cache",bid);
185+
const missing=routes.filter(r=>!fs.existsSync(path.join(root,(r==="/"?"/index":r).slice(1)+".cache")));
186+
console.log("prerendered routes:",routes.length);
187+
console.log("missing entries :",missing.length);
188+
if(missing.length){console.error("::error::"+missing.length+" prerendered route(s) have no cache entry");process.exit(1);}
189+
'
190+
158191
# `include-hidden-files` is load-bearing, not tidiness: the compiled
159192
# OpenNext config the deploy reads lives at `.open-next/.build/`, and
160193
# upload-artifact excludes dotted paths by default. Without it the
161194
# download succeeds, the deploy exits 1 on a missing config, and the
162195
# cause is three directories away from the message.
196+
# TEMPORARY (#261 round 3, reverted in this branch): `pull_request` added
197+
# so the artifact really round-trips, rather than being reasoned about.
163198
- name: Upload the Worker bundle
164-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
199+
if: >-
200+
(github.event_name == 'push' && github.ref == 'refs/heads/main')
201+
|| github.event_name == 'pull_request'
165202
uses: actions/upload-artifact@v7
166203
with:
167204
name: docs-worker
@@ -170,6 +207,82 @@ jobs:
170207
if-no-files-found: error
171208
retention-days: 3
172209

210+
# ==========================================================================
211+
# TEMPORARY JOB (#261 round 3). Reverted in this branch before review.
212+
#
213+
# It runs the deploy job's own inputs on the artifact CI actually produced:
214+
# download it, then call `opennextjs-cloudflare populateCache local` — the
215+
# exact step `opennextjs-cloudflare deploy` performs before `wrangler deploy`,
216+
# and for the static-assets cache a pure filesystem copy needing no
217+
# credentials. Then `wrangler deploy --dry-run` weighs what would be uploaded.
218+
#
219+
# This exists because "probably fine on a path that has never run" is the
220+
# reasoning that cost this repo a production outage on 2026-09-04.
221+
# ==========================================================================
222+
verify-deploy-inputs:
223+
name: TEMP — the artifact the deploy would publish
224+
needs: [build]
225+
if: github.event_name == 'pull_request'
226+
runs-on: ubuntu-latest
227+
permissions:
228+
contents: read
229+
steps:
230+
- uses: actions/checkout@v7
231+
- uses: pnpm/action-setup@v6
232+
- uses: actions/setup-node@v7
233+
with:
234+
node-version: 22
235+
cache: pnpm
236+
- run: pnpm install --frozen-lockfile
237+
238+
# Byte-for-byte what deploy-docs.yml does.
239+
- name: Download the Worker CI built and tested
240+
uses: actions/download-artifact@v8
241+
with:
242+
name: docs-worker
243+
path: apps/docs/.open-next
244+
245+
- name: The cache survived the artifact round-trip
246+
working-directory: apps/docs
247+
shell: bash
248+
run: |
249+
set -euo pipefail
250+
test -d .open-next/cache || { echo "::error::.open-next/cache did NOT survive the artifact"; exit 1; }
251+
test -f .open-next/.build/open-next.config.mjs || { echo "::error::compiled config did not survive"; exit 1; }
252+
echo "cache entries after download : $(find .open-next/cache -type f | wc -l)"
253+
echo "assets before populate : $(find .open-next/assets -type f | wc -l)"
254+
255+
- name: populateCache — the step the deploy runs before wrangler
256+
working-directory: apps/docs
257+
shell: bash
258+
run: |
259+
set -euo pipefail
260+
pnpm exec opennextjs-cloudflare populateCache local
261+
echo "assets after populate : $(find .open-next/assets -type f | wc -l)"
262+
echo "cache entries in assets : $(find .open-next/assets/cdn-cgi/_next_cache -type f | wc -l)"
263+
node -e '
264+
const fs=require("fs"),path=require("path");
265+
const bid=fs.readFileSync(".open-next/assets/BUILD_ID","utf8").trim();
266+
const m=JSON.parse(fs.readFileSync(".open-next/server-functions/default/apps/docs/.next/prerender-manifest.json","utf8"));
267+
const routes=Object.keys(m.routes||{});
268+
const root=path.join(".open-next/assets/cdn-cgi/_next_cache",bid);
269+
const missing=routes.filter(r=>!fs.existsSync(path.join(root,(r==="/"?"/index":r).slice(1)+".cache")));
270+
console.log("prerendered routes :",routes.length);
271+
console.log("servable from static assets :",routes.length-missing.length);
272+
console.log("missing :",missing.length);
273+
if(missing.length){console.error("::error::"+missing.length+" route(s) would 404 in production");process.exit(1);}
274+
'
275+
276+
# The number #262 needs, measured in CI on the bundle wrangler uploads
277+
# rather than scaled from a local ratio. Cloudflare rejects over 65536 KiB.
278+
- name: Weigh the bundle wrangler would upload
279+
working-directory: apps/docs
280+
shell: bash
281+
run: |
282+
set -euo pipefail
283+
pnpm exec wrangler deploy --dry-run --outdir "$RUNNER_TEMP/dryrun" 2>&1 | tee "$RUNNER_TEMP/dryrun.log"
284+
grep -E 'Total Upload|Read [0-9]+ files' "$RUNNER_TEMP/dryrun.log" | tee -a "$GITHUB_STEP_SUMMARY"
285+
173286
# Defect 1 of #269: `deploy-docs.yml` used to hang off `push: branches:
174287
# [main]` exactly as this workflow does, so the two ran in PARALLEL and a
175288
# commit that failed any gate above still deployed. There was no `needs:` and

0 commit comments

Comments
 (0)