Skip to content

Commit eae9a58

Browse files
ci: restore the Next.js cache on every run, save it on push and schedule only (#26984)
Part of #25452 (step 1, change (c), of the maintainer's reopen directive). Steps 2 and 3 stay on the card, so this PR does not close it. Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`ci.yml` 的 Build Docs 任务里,Next.js 缓存从一个合并式 `actions/cache` 拆成两步:每次运行都恢复,只在 `push` 和 `schedule` 运行上、精确键未命中时保存。 - 为什么改:PR 和合并队列每次运行都会存一份 0.2–0.9 GB 的 Next.js 缓存,只有它自己的 ref 能恢复,却占用同一个 10 GB 缓存池,把 `main` 的条目挤掉(池子读数 10.30 GB,其中 nextjs 占 4.98 GB)。pnpm store 在这个基点上已经是 PR/队列只恢复不保存,本 PR 不再改动。 - 风险与回滚:最坏情况是保存条件写错导致 `main` 也不再保存,docs 构建变冷但不会变红(保存步骤 `continue-on-error`);回滚就是 revert 这一个提交。 - 先看:`.github/workflows/ci.yml` 的 "Restore Next.js cache" 与 "Save Next.js cache" 两个步骤(只这一个文件)。 - 两分钟验证:打开本 PR 的 Build Docs 任务日志,应看到 "Restore Next.js cache" 正常恢复(Cache restored from key 一行),而 "Save Next.js cache" 显示为跳过;合并后 `main` 的 push 运行里该保存步骤应实际执行。 - 需要你做的:以 GOVERNED_APPROVERS 账号批准本 PR(Tier H,`.github/workflows/**`)。 ## What changes The maintainer's directive, verbatim, from the reopen comment on the card: > 可以:重开 #25452,挂回 pm:queue(domain:devx),按卡上的 (c)+(a) 先做:PR 和合并队列上 Next.js、pnpm 缓存只恢复不保存,再去掉远程缓存已覆盖的 build-core/typecheck/pack-smoke 种子;做完再测一次池子。 This PR does the first clause only: on `pull_request` and `merge_group`, the Next.js cache and the pnpm store restore and do not save, and `push` to `main` keeps saving. Dropping the build-core / typecheck / pack-smoke seeds (change (a)) is out of this PR: the #26070 ruling keeps pull-request runs without a remote-cache token, so those seeds are what PR builds restore, and the seat is taking (a) back to the maintainer. The seed steps are untouched. The pool limit is not raised. One file, `.github/workflows/ci.yml`, job `build-docs` (+39 / -5): - **Restore Next.js cache** (was "Setup Next.js cache"): `actions/cache@v6` becomes `actions/cache/restore@v6` with `id: nextjs-cache`. Path, key and restore-keys are byte-identical, so the entries `main` already holds stay valid. - **Save Next.js cache** (new, last step of the job): `actions/cache/save@v6`, `timeout-minutes: 5`, `continue-on-error: true`, key `steps.nextjs-cache.outputs.cache-primary-key`. - The comment above "Assert the docs app actually built" now names the restore step instead of the removed combined step. Its rule is kept: the cached path is still `apps/docs/.next/cache` only, so `.next/BUILD_ID` cannot come from the cache. The house pattern is `.github/actions/setup-workspace` (the pnpm store) and `ui-deps`'s "Save pnpm cache" in the same file: a restore step on every event, and a main-phase save gated on `push || schedule` and a miss of the exact key. This PR copies that pattern. The other spelling the dispatch offered, `actions/cache` with `lookup-only`, is not a restore-only mode: `lookup-only` "Check[s] if a cache entry exists ... without downloading the cache" (actions/cache v6 `restore/action.yml`), so it would have turned off the restore on PRs. That silent loss of the restore is the failure this PR has to avoid. ### Per step: the expression, why it restores on PR, why it saves on push | step | `if:` | `pull_request` | `merge_group` | `push` (main) | `schedule` | `workflow_dispatch` | |:--|:--|:--|:--|:--|:--|:--| | Restore Next.js cache | none | restores | restores | restores | restores | restores | | Save Next.js cache | `(github.event_name == 'push' \|\| github.event_name == 'schedule') && steps.nextjs-cache.outputs.cache-hit != 'true'` | skipped | skipped | saves on a miss | saves on a miss | skipped | - **The restore runs on every event because it has no `if:` at all.** Nothing on the restore path depends on an event name, so a misspelled condition cannot turn the restore off. - **The save is skipped on PR and queue runs.** `github.event_name` is the literal `pull_request` or `merge_group` there, so the left operand is false and the save does not run. On `push` and `schedule` the left operand is true. The right operand is false only on an exact-key hit, where the combined action's post step also skipped the save. - **What the combined action did on every event, kept for the events that still save:** - The save sits behind `success()`: `actions/cache@v6` declares `post-if: "success()"`, and a step with no status function in its `if:` gets `success()` implicitly. - The save uses the same key. The combined post step saved under the primary key it resolved at restore time, and `cache-primary-key` is that key. A key re-hashed after the build would hash `apps/docs/`, the tree the build writes into. - **Build Docs runs on `schedule` and `workflow_dispatch`.** Its filter output `docs` is `'true'` on both, because the paths-filter step is skipped there and `|| 'true'` applies. - After this change, `schedule` keeps saving: those runs are on `main`, so their entries can be restored everywhere. - `workflow_dispatch` no longer saves. That is the same rule `setup-workspace` applies to the pnpm store. A dispatch on a non-main ref would otherwise write an entry only that ref can restore. ### The pnpm store half: already restore-only on this base, so no step changes I re-derived `git grep -n 'actions/cache' origin/main -- .github/` at the dispatch's `aa00362f` and again on this head. The `pnpm-store-v3` sites the dispatch named are all **already** `actions/cache/restore@v6`, not plain `actions/cache`. The only save among them is gated like the house pattern: | site (this head) | job | step | `if:` | |:--|:--|:--|:--| | `ci.yml:3248` | `ui-deps` | Restore pnpm cache | none (all events) | | `ci.yml:3336` | `ui-deps` | Save pnpm cache | `(push \|\| schedule) && steps.pnpm-cache.outputs.cache-hit != 'true'` | | `ci.yml:3385` / `3643` / `3882` / `4204` | `type-check-ui` / `test-ui` / `lint-ui` / `build-e2e-ui` | Restore pnpm cache | none, restore only ("Build UI Deps saves the store") | | `governed-surface-guard.yml:241` | `governed-surface-guard` | Restore pnpm cache | `steps.toolchain.outputs.needed != 'false'`; the workflow triggers on `pull_request` and `merge_group` only, and it has no save step | | `.github/actions/setup-workspace/action.yml:104` / `:123` | every caller (12 in `lint.yml`, 7 in `ci.yml`, others) | Restore / Save pnpm store | save: `install != 'false' && save-store != 'false' && (push \|\| schedule) && cache-hit != 'true'` | `lint.yml` has no `pnpm-store-v3` key of its own: every job there goes through `setup-workspace`. No `setup-node` step caches pnpm (`cache: pnpm` matches nothing). So the pnpm half of the directive already holds, and the only step left was the Next.js cache. ## Tests This is a workflow-only diff with no package, script or test touched. The local evidence is the derived gate set, a YAML parse, and the control-character scan. The reading of record is the PR's own CI run. - YAML parse of `ci.yml` with the `yaml` package, listing `build-docs` steps: `Checkout repository | Setup workspace | Restore Next.js cache | Build Docs (the command production runs) | Assert the docs app actually built | Save Next.js cache`, and both new steps parse with the expressions above. - `grep -naP` for control bytes over `ci.yml`: no hits. - No ablation: the diff changes no tested behaviour (workflow config, which the os-dev contract exempts from ablation). ## Acceptance notes - **The CI log lines that prove "restores, does not save" on this PR: NOT MEASURED at the time of writing**, because the PR's run had not reported yet. Expected: in the Build Docs job, "Restore Next.js cache" prints a `Cache restored from key:` line (or a miss on a cold key), and "Save Next.js cache" is skipped. The first `main` push run after landing should run the save step on a miss. - **The pool re-reading (directive step 3) is not in this PR.** The seat container gets a 403 on `actions/cache/*`. - **Combined `actions/cache` steps that post-save on `pull_request` but are outside this directive.** They cache `.turbo/cache`, not Next.js or pnpm: `os-create-smoke.yml:95` (triggers `pull_request`, `schedule`, `workflow_dispatch`) and `pack-smoke-optin.yml:125` (`pull_request`, label opt-in), both with a per-sha key. `publish-smoke.yml:205` and `release.yml:1810` do not run on PR events. If the step-3 reading still shows `refs/pull/*` turbo entries from these two, that reading is the place to raise them. - `setup-pnpm`'s Corepack store save (`action.yml:228`) runs on any event, on a miss of a key that is content-addressed by the pnpm pin. A PR saves it only on the first run after a `packageManager` bump. That is one entry per pin, and it stays as it is. ## Verification Union of the derived gate set at head `4597816157`, from `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 53 commands, each exit code captured before any pipe. **Reconciliation, verbatim from `dispatch-gates --ran`:** `✓ dispatch-gates --ran: 53 derived famil(ies) accounted for — 50 run, 3 NOT-MEASURED (0 DERIVED from a recorded exit 3, 3 claimed).` <details><summary>Gate results at 4597816</summary> - **Exit 0 (50 commands).** The 18 direct `node` commands: closing-keyword-parity and its self-test, comment-mask-corpus, dts-emitted self-test, self-test-wired and its self-test, self-test-workflow-commands and its self-test, step-collectors and its self-test, whole-set-label-write and its self-test, and the self-tests of assert-turbo-remote-cache, plan-test-core-shards, prune-turbo-cache, scheduled-full-run and ci-failure, plus docs-audit check-drift-comment. The `pnpm check:*` commands: agent-test-spelling, aggregator-roster, ci-filter-parity, cross-package-test-inputs, declared-population-live, driver-memory-census, gitlink-declared, lean-entry-closure, node-version, nul-bytes, pm-close-cards, pm-dispatch-gates (fast tier: `1935 cases pass`), pm-expected-skips, pm-fleet-write-dispatch, pm-fleet-write-execute, pm-fleet-write-validate, pm-governed-merges, pm-issue-transfer, pm-post-stamped, pm-record-recognisers, pnpm-acquisition, pnpm-filter-targets, refd-timer-probe, required-contexts, shard-attestation, sourcemap-no-sources-content, stall-guard-budget, stall-guard-headroom, type-check-coverage, watch-hint-literal, workflow-status-functions and workflow-step-name-quoting. - **NOT MEASURED: `pnpm check:type-check-debt`.** Its closure build never got the shared verify lock: `os-verify-lock: VERDICT queue-timeout (exit 99) · never acquired · waited 540s`, with the lock held by another agent's run. This is a whole-workspace ledger ratchet, and a workflow-only diff cannot move it. CI's Lint & Repo Gates runs it. - **NOT MEASURED: `pnpm check:dts-closure` and `pnpm check:dual-build-cjs-loads`.** Both read `dist/`. This worktree's `dist/` is half-built: an earlier local run of type-check-debt was killed by my 400 s wrapper timeout during its closure build, and `@objectstack/organizations` never emitted `index.d.ts`. This diff touches no package. CI's Build Core builds from a fresh checkout. - **Not derived, not run:** actionlint. It is not in the derived set and not installed here. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYmTYfaPbLQD4BJfomW)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0b78a20 commit eae9a58

1 file changed

Lines changed: 39 additions & 5 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3006,8 +3006,17 @@ jobs:
30063006
- name: Setup workspace
30073007
uses: ./.github/actions/setup-workspace
30083008

3009-
- name: Setup Next.js cache
3010-
uses: actions/cache@v6
3009+
# The Next.js cache is RESTORED on every run and SAVED only on `push` and
3010+
# `schedule`, by "Save Next.js cache" at the end of this job -- the rule
3011+
# `.github/actions/setup-workspace` holds for the pnpm store, for the same
3012+
# reason: an entry saved on a pull request or merge-queue ref is
3013+
# restorable only from that ref, yet it counts toward the one 10 GB
3014+
# Actions cache pool and evicts `main`'s entries, which every pull request
3015+
# and queue run CAN restore. ⛔ No `pull_request` or `merge_group` run
3016+
# saves it.
3017+
- name: Restore Next.js cache
3018+
id: nextjs-cache
3019+
uses: actions/cache/restore@v6
30113020
with:
30123021
path: apps/docs/.next/cache
30133022
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('apps/docs/**.[jt]s', 'apps/docs/**.[jt]sx', 'content/**/*.mdx') }}
@@ -3073,9 +3082,9 @@ jobs:
30733082
# this card exists to catch, arriving AFTER the gate reported green.
30743083
# Assert the artifact rather than trusting the exit code.
30753084
#
3076-
# `.next/BUILD_ID` cannot be a leftover from the cache step above: that
3077-
# step stores `apps/docs/.next/cache` ONLY, so nothing outside that
3078-
# subtree is ever restored. ⛔ Widening that cache path to
3085+
# `.next/BUILD_ID` cannot be a leftover from the cache restore above:
3086+
# the Next.js cache steps store `apps/docs/.next/cache` ONLY, so nothing
3087+
# outside that subtree is ever restored. ⛔ Widening that cache path to
30793088
# `apps/docs/.next` would let this assertion pass without a build.
30803089
- name: Assert the docs app actually built
30813090
run: |
@@ -3086,6 +3095,31 @@ jobs:
30863095
fi
30873096
echo "Built docs app: BUILD_ID=$(cat apps/docs/.next/BUILD_ID)"
30883097
3098+
# The save half of "Restore Next.js cache" (its comment says why it runs
3099+
# on `push` and `schedule` only). It reproduces what the combined
3100+
# `actions/cache` post step did on those events, three ways:
3101+
# - no status function in `if:`, so it runs only when every step above
3102+
# succeeded -- the combined action's `post-if: success()`;
3103+
# - skipped on an exact hit, which has nothing new to write;
3104+
# - saved under `cache-primary-key`, the key the restore step resolved
3105+
# BEFORE the build, which is the key the combined action saved under.
3106+
# A key re-hashed here would hash `apps/docs/`, the tree the build
3107+
# has just written into, so it could differ from the key the restore
3108+
# looked up.
3109+
# A main-phase step, bounded and `continue-on-error` like the ui jobs'
3110+
# saves: a stalled or failed upload costs the next run time and says
3111+
# nothing about the docs build.
3112+
- name: Save Next.js cache
3113+
if: >-
3114+
(github.event_name == 'push' || github.event_name == 'schedule')
3115+
&& steps.nextjs-cache.outputs.cache-hit != 'true'
3116+
uses: actions/cache/save@v6
3117+
timeout-minutes: 5
3118+
continue-on-error: true
3119+
with:
3120+
path: apps/docs/.next/cache
3121+
key: ${{ steps.nextjs-cache.outputs.cache-primary-key }}
3122+
30893123
# ── THE ui/ JOBS (objectstack#25697, epic objectstack#25677) ──────────────
30903124
# objectui is imported under `ui/` as a subtree. These four jobs are its CI,
30913125
# ported from objectui's own ci.yml (`Type Check`, `Test (shard n/16)`,

0 commit comments

Comments
 (0)