Skip to content

Commit 1676de0

Browse files
os-zhuangclaude
andauthored
chore(ci): cut PR wall-clock — dedupe dogfood out of Test Core, shard the gate 2-way, cache lint's build (#3622)
* chore(ci): cut PR wall-clock ~9.5min → ~5-6min — dedupe dogfood, shard it, cache lint's build Three data-driven fixes from job-level timing of recent PR runs: 1. Test Core re-ran the whole ~7.5-minute dogfood suite that the dedicated Dogfood job was already running in parallel (both gate on the same `core` filter). Exclude @objectstack/dogfood from both the PR --affected run and the push run; the Dogfood job remains the sole (and unconditional) runner of the suite. Verified locally: turbo unions inclusive filters and then subtracts `!` negations, so `--affected --filter=!@objectstack/dogfood` is exactly "affected minus dogfood" (140 → 137 tasks, nothing else lost). 2. The Dogfood job itself was the workflow's longest pole (7m34s for the ~60-file suite on one 4-vCPU runner). Shard it 2-way with vitest's deterministic file-level --shard, passed through turbo (pass-through args are part of the turbo task hash, so each shard caches independently — verified: distinct hashes per shard). The `objectstack verify` CLI step is shard-independent and runs on shard 1 only. NOTE: if branch protection requires "Dogfood Regression Gate", the required check must be renamed to the two sharded names. 3. lint.yml's "TypeScript Type Check" job runs a full workspace build but was the only build-running job with NO turbo cache step — it rebuilt everything from scratch every run (4m38s observed, vs 51s for the same build in ci.yml's cached Build Core). Add the same .turbo/cache step. Also: lint.yml and codeql.yml had no concurrency group, so superseded runs of both (they trigger on every PR sync) kept burning runners and delaying the queue. Add the same cancel-in-progress policy ci.yml already documents. Full-chain smoke: `turbo run test --filter=@objectstack/dogfood -- --shard=1/30` runs green end-to-end (turbo → pnpm → vitest, 61 tasks OK). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECTCrcCdZpCHw5zFSgcmGt * chore: empty changeset — CI-only change, releases nothing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECTCrcCdZpCHw5zFSgcmGt --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0a6fb1e commit 1676de0

4 files changed

Lines changed: 65 additions & 9 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
CI-only: exclude the dogfood suite from Test Core (the dedicated Dogfood job runs it), shard the Dogfood gate 2-way, add the missing turbo cache to lint's typecheck job, and cancel superseded lint/CodeQL runs. Releases nothing.

.github/workflows/ci.yml

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,25 @@ jobs:
130130
# --concurrency=4: turbo's default (10) oversubscribes the 4-vCPU
131131
# hosted runner; matching the core count bounds peak memory and the
132132
# job is CPU-bound anyway.
133+
# !@objectstack/dogfood: the ~7½-minute dogfood suite is the dedicated
134+
# Dogfood job's whole purpose, and both jobs run under the same `core`
135+
# filter — without the exclusion every core PR executed the suite twice
136+
# in parallel, and it dominated this job's critical path. The exclusion
137+
# subtracts from the affected set (verified: turbo unions inclusive
138+
# filters, then applies `!` negations to the result).
133139
- name: Run affected tests (PR)
134140
if: github.event_name == 'pull_request'
135141
env:
136142
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }}
137-
run: pnpm turbo run test --affected --concurrency=4
143+
run: pnpm turbo run test --affected --filter=!@objectstack/dogfood --concurrency=4
138144

139145
# Push to main: full run, but exclude spec's plain test task — the
140146
# coverage step below executes the exact same 250-file spec suite once,
141-
# with coverage. Previously the spec suite ran twice per push.
147+
# with coverage. Previously the spec suite ran twice per push. Dogfood is
148+
# excluded for the same reason as the PR step: the Dogfood job runs it.
142149
- name: Run all tests (push)
143150
if: github.event_name == 'push'
144-
run: pnpm turbo run test --filter=!@objectstack/spec --concurrency=4
151+
run: pnpm turbo run test --filter=!@objectstack/spec --filter=!@objectstack/dogfood --concurrency=4
145152

146153
- name: Generate coverage report
147154
if: github.event_name == 'push'
@@ -156,12 +163,22 @@ jobs:
156163
retention-days: 30
157164

158165
dogfood:
159-
name: Dogfood Regression Gate
166+
# Sharded 2-way: the suite is ~60 independent test files, each booting its
167+
# own in-process app, and a single 4-vCPU runner needed ~7½ minutes for the
168+
# lot — the longest pole in the whole workflow. vitest partitions the file
169+
# list deterministically across shards; both shards must pass. If branch
170+
# protection lists "Dogfood Regression Gate" as a required check, it must be
171+
# updated to the two sharded check names.
172+
name: Dogfood Regression Gate (${{ matrix.shard }}/2)
160173
needs: filter
161174
if: needs.filter.outputs.core == 'true'
162175
runs-on: ubuntu-latest
163176
permissions:
164177
contents: read
178+
strategy:
179+
fail-fast: false
180+
matrix:
181+
shard: [1, 2]
165182

166183
steps:
167184
- name: Checkout repository
@@ -191,30 +208,37 @@ jobs:
191208
restore-keys: |
192209
${{ runner.os }}-pnpm-store-v3-
193210
211+
# Shard-scoped key: the turbo test hash differs per shard (pass-through
212+
# args are part of the task hash), and two same-named matrix jobs would
213+
# otherwise race to save one cache entry per sha.
194214
- name: Setup Turbo cache
195215
uses: actions/cache@v6
196216
with:
197217
path: .turbo/cache
198-
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
218+
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-${{ github.sha }}
199219
restore-keys: |
200-
${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-
201-
${{ runner.os }}-turbo-${{ github.job }}-
220+
${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-
221+
${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-
202222
203223
- name: Install dependencies
204224
run: pnpm install --frozen-lockfile
205225

206226
# Boots real example apps in-process (in-memory SQLite) and exercises them
207227
# through the real HTTP + service stack — catches runtime regressions that
208228
# build / unit tests / spec-liveness pass over (e.g. the #2018 tz-bucketing
209-
# break, which was green on every static gate).
229+
# break, which was green on every static gate). The `--` args reach the
230+
# package's `vitest run` and are hashed into the turbo task, so each
231+
# shard caches independently.
210232
- name: Boot example apps and exercise real user flows
211-
run: pnpm turbo run test --filter=@objectstack/dogfood
233+
run: pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/2
212234

213235
# Replaces the former auto-verify dogfood tests: runs the published
214236
# `objectstack verify` engine over each example app through the CLI —
215237
# auto-derived CRUD round-trip fidelity + the cross-owner RLS invariant.
216238
# Exits non-zero on a real runtime failure, so it gates like the tests did.
239+
# Not shard-dependent, so shard 1 alone runs it.
217240
- name: Verify example apps via the `objectstack verify` CLI
241+
if: matrix.shard == 1
218242
run: |
219243
pnpm turbo run build --filter=@objectstack/cli
220244
for app in examples/app-crm examples/app-showcase; do

.github/workflows/codeql.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ on:
1111
# Run at 02:00 UTC every Monday
1212
- cron: '0 2 * * 1'
1313

14+
# A superseded analysis of an outdated commit has no value — cancel it. PR runs
15+
# group by PR number; push/schedule runs group by ref, so a newer main push
16+
# cancels only an in-flight main analysis (the newer commit gets analyzed).
17+
concurrency:
18+
group: codeql-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
19+
cancel-in-progress: true
20+
1421
jobs:
1522
analyze:
1623
name: Analyze

.github/workflows/lint.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ on:
88
branches:
99
- main
1010

11+
# Same policy as ci.yml: superseded runs on the same PR/branch waste runners
12+
# and delay feedback; cancel them. Push runs to main group by commit ref, so an
13+
# in-flight main run is cancelled only by a newer main push.
14+
concurrency:
15+
group: lint-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
16+
cancel-in-progress: true
17+
1118
jobs:
1219

1320
lint:
@@ -133,6 +140,20 @@ jobs:
133140
restore-keys: |
134141
${{ runner.os }}-pnpm-store-v3-
135142
143+
# This job runs the full workspace build below ("Build workspace
144+
# packages"); without a restored turbo cache that step rebuilt every
145+
# package from scratch on every run (~4½ min) while ci.yml's jobs — which
146+
# do carry this cache — finished the same build in under a minute. Same
147+
# key scheme as ci.yml so the fallback prefix can also hit main's caches.
148+
- name: Setup Turbo cache
149+
uses: actions/cache@v6
150+
with:
151+
path: .turbo/cache
152+
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
153+
restore-keys: |
154+
${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-
155+
${{ runner.os }}-turbo-${{ github.job }}-
156+
136157
- name: Install dependencies
137158
run: pnpm install --frozen-lockfile
138159

0 commit comments

Comments
 (0)