From 2ae441384e4d57dbfd749a4390949183a49aba7e Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:41:20 -0700 Subject: [PATCH] fix(ci): regenerate workspace codegen even when the node_modules cache hits main has been red for four consecutive runs with nothing wrong in the tree, and every PR fails validate-code regardless of what it changes -- including a test-only PR touching no UI file. setup-workspace restores a node_modules cache and, on a hit, skips `npm ci` entirely. Skipping `npm ci` skips every workspace lifecycle script, and apps/loopover-ui's `postinstall: fumadocs-mdx` is what generates `.source/` -- the directory tsconfig maps `collections/*` onto. `.source/` is gitignored and is not among the cached paths, so on a cache hit the UI typechecks against modules that do not exist: src/lib/docs-client-loader.tsx(1,32): error TS2307: Cannot find module 'collections/browser' src/lib/docs-source.server.ts(1,22): error TS2307: Cannot find module 'collections/server' The cache key includes the lockfile, which is why it started when it did: a lockfile change mints a fresh key, the first run misses and installs and passes and saves, and every run after it hits and fails. Green through 507960134, red from 40133d789 on, with no relevant change between them. Same shape as the @eslint/js hoist bug the cache step already documents -- it reproduces on no developer machine, because a real `npm ci` always runs postinstall. Reproduced by simulating the cache-hit state: removing apps/loopover-ui/.source with node_modules intact gives exactly those errors, and running the codegen clears them. REGENERATED, NOT CACHED. Adding `.source` to the cache paths is the smaller fix and the wrong one: the key derives from the manifests and lockfile, while `.source` derives from source.config.ts and content/docs/**. A cached copy would serve a docs collection generated from different MDX than the commit under test -- a stale pass, which is worse than an honest failure. Closes #9975 --- .github/actions/setup-workspace/action.yml | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/actions/setup-workspace/action.yml b/.github/actions/setup-workspace/action.yml index dec162a55f..29e350a3a7 100644 --- a/.github/actions/setup-workspace/action.yml +++ b/.github/actions/setup-workspace/action.yml @@ -91,6 +91,31 @@ runs: echo "::error::npm ci failed after 3 attempts" exit 1 + # UNCONDITIONAL -- this is the whole point, so read the `if:` above before adding one here. + # + # A cache HIT skips `npm ci`, and skipping `npm ci` skips every workspace's lifecycle scripts. The one + # that matters is `apps/loopover-ui`'s `postinstall: fumadocs-mdx`, which generates `.source/` -- the + # directory `tsconfig.json` maps `collections/*` onto. So on a cache hit the UI typechecked against + # modules that did not exist: + # + # src/lib/docs-client-loader.tsx(1,32): error TS2307: Cannot find module 'collections/browser' + # src/lib/docs-source.server.ts(1,22): error TS2307: Cannot find module 'collections/server' + # + # This is the same shape as the `@eslint/js` hoist bug the cache step above documents: it reproduces on + # no developer machine, because a real `npm ci` always runs postinstall. It first bit when a lockfile + # change minted a fresh cache key -- the first run MISSED, installed, generated `.source`, passed, and + # saved; every run after that HIT and failed, so `main` went red four runs in a row while nothing in the + # tree had changed. + # + # REGENERATED, NOT CACHED. Adding `.source` to the cache paths would look like a smaller fix and would be + # wrong: the cache key is derived from the manifests and lockfile, while `.source` is derived from + # `source.config.ts` and `content/docs/**`. Caching it would serve a docs collection generated from + # different MDX than the commit under test -- a stale pass, which is worse than the honest failure. It is + # cheap to regenerate, so it is regenerated every time. + - name: Generate workspace codegen + shell: bash + run: npm run postinstall --workspace @loopover/ui + # Placed immediately after install (not as an automatic post-job hook) so a cache is only ever # saved once npm ci has actually succeeded -- a job that fails here never reaches this step, so a # broken/partial node_modules can never get written to the cache for a future run to inherit.