mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
20e5c21c07
The repo's Actions cache holds 21 entries / 10,878,231,374 bytes against a
10 GB per-repo limit, so LRU eviction is live and is throwing away useful
entries.
There are exactly two cache keys, same content hash, differing only by OS:
node-cache-Linux-x64-pnpm-428a04cc... 1 copy, on refs/heads/main, 511 MB
node-cache-Windows-x64-pnpm-428a04cc... 20 copies, each on a different
refs/pull/N/merge, ~518 MB each
= 9.65 GB, 95.3% of the budget
Mechanism: an Actions cache entry is scoped to a ref. A PR run can read an
entry saved on its base branch, but not one saved by another PR, and it saves
into its own refs/pull/N/merge scope. windows-dev-env.yml triggers on
pull_request only, so there was no entry on refs/heads/main for a PR to
restore from: every PR missed, and every PR then saved its own ~518 MB copy
under the same key in its own dead scope. lint.yml does not have this problem
precisely because it has a push-to-main leg.
Two changes:
1. windows-dev-env.yml gains push: branches: [main]. One base-branch entry
then serves every PR and no PR saves its own. Two side effects the guard
test in scripts/__tests__/main-branch-ci-coverage.test.ts pins, both fixed
here: the concurrency group must vary per commit on main (it was
github.ref, one group for the whole branch), and a job on the push path
may not be unconditionally continue-on-error (a run reporting success over
a failed step). The canary stays report-only on PRs, which is what its
header actually argues for, and renders honestly on main. No step in that
workflow reads github.base_ref or github.event.pull_request, so nothing
needed gating.
2. lint.yml's unit job caches node_modules/.vite. Measured on a warm local
checkout: 302 MB, all of it esbuild pre-bundles under
.vite/vitest/<hash>/{deps,deps_ssr}. All four shards rebuild it cold on
every PR today. Restore always, save only on main, so this can never
repeat the per-PR-scope leak above: four entries total, 1.21 GB worst
case, restorable by every PR from its base branch.
Verified: actionlint clean on both files (and red on a deliberately broken
copy, as a negative control); both parse as YAML; the main-branch CI coverage
guard is 6/6 green, and goes red on exactly the two assertions above when the
concurrency group and continue-on-error are reverted. The unit, packages and
apps jobs keep no job-level if:, so they still run on every PR to main and
release.
The 20 orphaned Windows entries are being deleted separately by the operator;
nothing here reaps them.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
177 lines
8.7 KiB
YAML
177 lines
8.7 KiB
YAML
name: Windows dev-env smoke
|
|
|
|
# Non-blocking canary for the DEFAULT (non-Nix) contributor setup on native Windows.
|
|
#
|
|
# WHY: every other job in this repo runs on ubuntu-latest, so nothing validates
|
|
# Windows — while Windows support is real and actively maintained:
|
|
# `.claude/skills/dev-server/scripts/defender-exclusions.ps1`, `win32` branches in
|
|
# `scripts/test-unit-run.mjs`, `scripts/test-perf/bench.mjs`,
|
|
# `.claude/skills/dev-server/console.mjs` and `.../scripts/worktree.mjs`, plus
|
|
# `it.skipIf(process.platform === 'win32')` guards in `scripts/__tests__/`.
|
|
# A supported platform verified by nothing is how a contributor's first hour gets
|
|
# spent on a break nobody else can reproduce.
|
|
#
|
|
# THE SPECIFIC EXPOSURE this is aimed at: `pnpm install` runs two lifecycle hooks —
|
|
# `preinstall` (`npx only-allow pnpm`) and `postinstall` (`pnpm run db:generate`,
|
|
# i.e. `node scripts/generate-slim-schema.js && prisma generate --no-hints`). The
|
|
# Makefile carried `# TODO fix postinstall on git bash` against exactly that path.
|
|
# That target was ALSO dead for everyone — `npm i` exits 1 under `only-allow` — so
|
|
# the hook path went a long time without being exercised on Windows by anybody.
|
|
# Un-breaking the target means the next Windows contributor is the first to walk
|
|
# it in a long while. This job walks it first.
|
|
#
|
|
# BOTH SHELLS ON PURPOSE: `pwsh` is the Windows default, `bash` is Git Bash — and
|
|
# the TODO above was Git-Bash-specific. A pwsh-only job would not reproduce it, and
|
|
# would report green over the one case we have written evidence about.
|
|
#
|
|
# DELIBERATELY NON-BLOCKING ON PULL REQUESTS to start. A gate that lands red on day
|
|
# one and stops unrelated PRs trains everyone to click through, which is strictly
|
|
# worse than no gate. Turn it off once this has been green long enough to mean
|
|
# something. It is NOT non-blocking on a push to `main` — see the job's
|
|
# `continue-on-error` comment for why the two cases differ.
|
|
#
|
|
# DELIBERATELY NOT starting the compose stack: those are Linux containers, and
|
|
# Docker on Windows runners is slow and flaky — it would buy noise, not signal.
|
|
# This covers everything up to `pnpm dev`; the services half is Linux-identical
|
|
# and already exercised by the ubuntu jobs.
|
|
#
|
|
# COST: `windows-latest` is a standard GitHub-hosted runner, which is free for
|
|
# public repositories. This adds no Actions minutes.
|
|
#
|
|
# ── ALSO RUNS ON PUSH TO `main`, AND THAT IS A CACHE FIX ─────────────────────
|
|
#
|
|
# `setup-node` with `cache: pnpm` writes an Actions cache entry keyed
|
|
# `node-cache-Windows-x64-pnpm-<lockfile-hash>`. Actions caches are SCOPED TO A
|
|
# REF: a PR run can READ an entry saved on its base branch, but it cannot read
|
|
# one saved on another PR, and it saves into its own `refs/pull/N/merge` scope.
|
|
#
|
|
# With this workflow `pull_request`-only there was no entry on `refs/heads/main`
|
|
# for a PR to restore from, so EVERY PR missed, re-resolved the whole store and
|
|
# then saved its own ~518 MB copy under the same key in its own scope. Measured
|
|
# 2026-08-25: 20 such copies, one per open PR, 9.65 GB — 95.3% of the repo's
|
|
# 10 GB cache budget, against ONE 511 MB Linux entry on `main` from `lint.yml`.
|
|
# The repo was over the limit, so LRU eviction was live and was evicting the
|
|
# useful entries to keep 20 identical useless ones.
|
|
#
|
|
# `lint.yml` never had this problem, and the reason is exactly the leg added
|
|
# below: it has a `push: [main]` trigger, so one base-branch entry serves every
|
|
# PR and no PR ever saves its own. This makes that true here too. One entry,
|
|
# reused by every PR, refreshed whenever `main` moves.
|
|
#
|
|
# The two matrix shells share one cache key (same OS, arch and lockfile), so on
|
|
# `main` they race to save it and one logs "another job may be creating this
|
|
# cache". That is the intended outcome — one entry is the point — not a defect.
|
|
#
|
|
# Nothing in this workflow reads `github.base_ref` or `github.event.pull_request`
|
|
# (both are empty/null on a push), so no step needs gating. That is not luck:
|
|
# `scripts/__tests__/main-branch-ci-coverage.test.ts` asserts it as a repo-wide
|
|
# relationship over every push-reachable job.
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, release]
|
|
push:
|
|
branches: [main]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# On a PR, unchanged: `github.ref` is `refs/pull/N/merge`, so a new push to the
|
|
# branch cancels the in-flight run.
|
|
#
|
|
# On a push to `main` it MUST NOT be `github.ref` — that is one group for the
|
|
# whole branch, so a busy `main` would cancel its own runs and only the last
|
|
# commit would ever be checked (and the losers would render as `cancelled`,
|
|
# which is indistinguishable from a real failure). Same expression as `lint.yml`,
|
|
# for the same reason, and pinned by the same test.
|
|
concurrency:
|
|
group: windows-dev-env-${{ github.event_name == 'pull_request' && github.ref || github.sha }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
smoke:
|
|
name: Windows dev-env (${{ matrix.shell }})
|
|
runs-on: windows-latest
|
|
# REPORT-ONLY ON PULL REQUESTS, REAL VERDICT ON `main` — the same split
|
|
# `lint.yml`'s `unit` job makes, for the same reason. The header's
|
|
# non-blocking rationale is explicitly about not stopping UNRELATED PRs over
|
|
# a canary that might be red on day one. On a push the merge has already
|
|
# happened, so there is no unrelated work to stop and nothing to block; what
|
|
# `continue-on-error: true` would buy there is a run reporting `success`
|
|
# while the step underneath failed, which is a green that has to be
|
|
# disbelieved. `main` carries no required_status_checks either way.
|
|
continue-on-error: ${{ github.event_name == 'pull_request' }}
|
|
timeout-minutes: 25
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
shell: [pwsh, bash]
|
|
defaults:
|
|
run:
|
|
shell: ${{ matrix.shell }}
|
|
|
|
steps:
|
|
# `submodules: true` is CI's equivalent of the documented
|
|
# `git submodule update --init event-engine-common`. Without it the install
|
|
# fails in a way that looks like a code defect rather than a missing checkout
|
|
# step — the exact confusion that cost a session earlier.
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
submodules: true
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version-file: .nvmrc
|
|
cache: pnpm
|
|
|
|
# Report rather than assume. `engines.node` is ADVISORY — pnpm prints
|
|
# `WARN Unsupported engine` and exits 0 on a wrong major — so a mismatch here
|
|
# would otherwise surface later as unrelated-looking failures.
|
|
- name: Toolchain actually in use
|
|
run: |
|
|
node --version
|
|
pnpm --version
|
|
|
|
# THE STEP UNDER TEST. Both lifecycle hooks run here.
|
|
- name: pnpm install (runs preinstall + postinstall)
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# POSITIVE CONTROL — the half that makes the green mean something.
|
|
#
|
|
# `pnpm install` exiting 0 is NOT proof the postinstall hook did its job: a
|
|
# hook that silently no-ops also exits 0, and the missing client then surfaces
|
|
# much later as a confusing runtime error. So assert the artifact.
|
|
#
|
|
# This particular artifact is a genuine control because
|
|
# `packages/civitai-db-schema/prisma/schema.prisma` is GITIGNORED — it cannot
|
|
# arrive from the checkout, so its presence proves the hook executed here.
|
|
#
|
|
# Runs under `node` rather than the matrix shell on purpose: this checks an
|
|
# artifact, not shell behaviour, and it keeps the assertion free of pwsh-vs-bash
|
|
# quoting differences.
|
|
- name: Assert postinstall actually generated the slim schema
|
|
shell: node {0}
|
|
run: |
|
|
const fs = require('fs');
|
|
const f = 'packages/civitai-db-schema/prisma/schema.prisma';
|
|
if (!fs.existsSync(f)) {
|
|
console.log(`::error title=postinstall did not run::${f} is absent after \`pnpm install\`. It is gitignored, so it can only come from the postinstall hook (\`pnpm run db:generate\`). The install reported success, so the hook failed silently or was skipped.`);
|
|
process.exit(1);
|
|
}
|
|
const lines = fs.readFileSync(f, 'utf8').split('\n').length;
|
|
console.log(`${f}: ${lines} lines`);
|
|
// A truncated or empty generate also exits 0. 50 is far below any real
|
|
// schema and far above an accidental stub, so it separates the two
|
|
// without pinning a number that ordinary schema churn would trip.
|
|
if (lines < 50) {
|
|
console.log(`::error title=slim schema looks truncated::${f} exists but has only ${lines} lines.`);
|
|
process.exit(1);
|
|
}
|
|
|
|
# Standalone re-run: the hook working once inside `pnpm install` does not mean
|
|
# a contributor can run it by hand, which the docs tell them to do.
|
|
- name: db:generate standalone
|
|
run: pnpm run db:generate
|