Skip to content

Commit d64739c

Browse files
authored
fix(ci): unblock @next/swc, lockfile-keyed node_modules, per-image runner sizing (#5945)
* fix(ci): key node_modules sticky disk on the lockfile hash * improvement(ci): per-image Blacksmith runner sizing + cold-build memory preflight * fix(deps): exclude @next/swc binaries from the release-age gate * fix(deps): pin @next/swc binaries so frozen installs get a compiler * docs(ci): explain ARM runner sizing rationale
1 parent 17d7779 commit d64739c

6 files changed

Lines changed: 90 additions & 23 deletions

File tree

.github/workflows/ci.yml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
name: Build Dev ECR
105105
needs: [detect-version, migrate-dev]
106106
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
107-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || matrix.gh_runner }}
107+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }}
108108
timeout-minutes: 30
109109
permissions:
110110
contents: read
@@ -115,18 +115,25 @@ jobs:
115115
include:
116116
# Only the app image needs the paid 8-core/32 GB runner: next build
117117
# exhausts the free 16 GB one (exit 137). The others build in <5 min.
118+
# bs_runner mirrors that per-image sizing on Blacksmith — a single
119+
# pinned tier put every image on 8 vCPU, where the non-app builds idle
120+
# at 12-15% CPU and under 10% memory.
118121
- dockerfile: ./docker/app.Dockerfile
119122
ecr_repo_secret: ECR_APP
120123
gh_runner: linux-x64-8-core
124+
bs_runner: blacksmith-8vcpu-ubuntu-2404
121125
- dockerfile: ./docker/db.Dockerfile
122126
ecr_repo_secret: ECR_MIGRATIONS
123127
gh_runner: ubuntu-latest
128+
bs_runner: blacksmith-2vcpu-ubuntu-2404
124129
- dockerfile: ./docker/realtime.Dockerfile
125130
ecr_repo_secret: ECR_REALTIME
126131
gh_runner: ubuntu-latest
132+
bs_runner: blacksmith-4vcpu-ubuntu-2404
127133
- dockerfile: ./docker/pii.Dockerfile
128134
ecr_repo_secret: ECR_PII
129135
gh_runner: ubuntu-latest
136+
bs_runner: blacksmith-4vcpu-ubuntu-2404
130137
steps:
131138
- name: Checkout code
132139
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
@@ -215,7 +222,7 @@ jobs:
215222
if: >-
216223
github.event_name == 'push' &&
217224
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
218-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || matrix.gh_runner }}
225+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }}
219226
timeout-minutes: 30
220227
permissions:
221228
contents: read
@@ -229,18 +236,22 @@ jobs:
229236
ghcr_image: ghcr.io/simstudioai/simstudio
230237
ecr_repo_secret: ECR_APP
231238
gh_runner: linux-x64-8-core
239+
bs_runner: blacksmith-8vcpu-ubuntu-2404
232240
- dockerfile: ./docker/db.Dockerfile
233241
ghcr_image: ghcr.io/simstudioai/migrations
234242
ecr_repo_secret: ECR_MIGRATIONS
235243
gh_runner: ubuntu-latest
244+
bs_runner: blacksmith-2vcpu-ubuntu-2404
236245
- dockerfile: ./docker/realtime.Dockerfile
237246
ghcr_image: ghcr.io/simstudioai/realtime
238247
ecr_repo_secret: ECR_REALTIME
239248
gh_runner: ubuntu-latest
249+
bs_runner: blacksmith-4vcpu-ubuntu-2404
240250
- dockerfile: ./docker/pii.Dockerfile
241251
ghcr_image: ghcr.io/simstudioai/pii
242252
ecr_repo_secret: ECR_PII
243253
gh_runner: ubuntu-latest
254+
bs_runner: blacksmith-4vcpu-ubuntu-2404
244255
steps:
245256
- name: Checkout code
246257
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
@@ -387,7 +398,7 @@ jobs:
387398
# never moves a documented tag.
388399
build-ghcr-arm64:
389400
name: Build ARM64 (GHCR Only)
390-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404-arm' || matrix.gh_runner }}
401+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }}
391402
timeout-minutes: 30
392403
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
393404
permissions:
@@ -396,19 +407,27 @@ jobs:
396407
strategy:
397408
fail-fast: false
398409
matrix:
410+
# Non-app images sit at 4 vCPU rather than the finer x64 split: the ARM
411+
# sizing data is job-level (8 -> 4 for the whole matrix), not per-image,
412+
# and this job only runs on push to main — an unprovisioned label would
413+
# hang a release in `queued` rather than fail a PR.
399414
include:
400415
- dockerfile: ./docker/app.Dockerfile
401416
image: ghcr.io/simstudioai/simstudio
402417
gh_runner: linux-arm64-8-core
418+
bs_runner: blacksmith-8vcpu-ubuntu-2404-arm
403419
- dockerfile: ./docker/db.Dockerfile
404420
image: ghcr.io/simstudioai/migrations
405421
gh_runner: ubuntu-24.04-arm
422+
bs_runner: blacksmith-4vcpu-ubuntu-2404-arm
406423
- dockerfile: ./docker/realtime.Dockerfile
407424
image: ghcr.io/simstudioai/realtime
408425
gh_runner: ubuntu-24.04-arm
426+
bs_runner: blacksmith-4vcpu-ubuntu-2404-arm
409427
- dockerfile: ./docker/pii.Dockerfile
410428
image: ghcr.io/simstudioai/pii
411429
gh_runner: ubuntu-24.04-arm
430+
bs_runner: blacksmith-4vcpu-ubuntu-2404-arm
412431

413432
steps:
414433
- name: Checkout code

.github/workflows/docs-embeddings.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ permissions:
1010
jobs:
1111
process-docs-embeddings:
1212
name: Process Documentation Embeddings
13-
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
13+
# Network-bound on the embeddings API: ~9% CPU and ~3% peak memory on 8 vCPU.
14+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
1415
timeout-minutes: 30
1516
if: github.ref == 'refs/heads/main'
1617

.github/workflows/test-build.yml

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ jobs:
3131
# namespace on top: untrusted fork runs must never share a cache with
3232
# push runs (whose caches feed production image builds) or with trusted
3333
# internal-PR runs.
34+
#
35+
# node_modules also keys on the lockfile hash: a sticky disk is a mutable
36+
# volume, and `bun install --frozen-lockfile` adds what the lockfile needs
37+
# without pruning what it dropped, so branches on different lockfiles were
38+
# contaminating each other (a stale @next/swc 16.2.6 outlived the 16.2.11
39+
# bump). The bun and Turbo caches are content/hash-addressed, so they stay
40+
# shared — that is what keeps a fresh node_modules disk cheap to fill.
3441
- name: Mount Bun cache
3542
uses: ./.github/actions/cache-mount
3643
with:
@@ -42,7 +49,7 @@ jobs:
4249
uses: ./.github/actions/cache-mount
4350
with:
4451
provider: ${{ vars.CI_PROVIDER }}
45-
key: ${{ github.repository }}-node-modules-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
52+
key: ${{ github.repository }}-node-modules-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ hashFiles('bun.lock') }}
4653
path: ./node_modules
4754

4855
- name: Mount Turbo cache
@@ -193,19 +200,16 @@ jobs:
193200
# Next.js production build, in parallel with lint + tests. Sticky disks are
194201
# cloned from the last committed snapshot per job and committed last-writer-
195202
# wins, so concurrent mounts are safe. The bun/node_modules disks are shared
196-
# with test-build (identical content from the same lockfile — LWW loss is
197-
# harmless), but the Turbo cache gets its own key: with a shared key, only
198-
# the last committer's new entries survive each run, so the test and build
199-
# Turbo entries would evict each other nondeterministically.
200-
# Same next build as the app image, so it needs a large runner. Peak RSS
201-
# tracks Turbo/Turbopack cache warmth, and it is the COLD case that sizes the
202-
# runner: warm peaks ~12 GB, partial ~28 GB, and a cold full rebuild peaked
203-
# 51 GB. The 8vcpu tier only has 30.4 GB, so cold-cache runs OOM-killed the VM
204-
# (oom_count 1, memory p100 ~30.5 GB, ~99% of the tier) — the job burned 8-15
205-
# min and died, while warm runs finished under 8 min. NODE_OPTIONS'
206-
# --max-old-space-size only caps Node's JS heap; the bulk is native Turbopack
207-
# worker memory, so the cap cannot prevent this. 16vcpu = 60.8 GB fits the
208-
# 51 GB cold peak with headroom. Keep the test job on 8vcpu; its memory is fine.
203+
# with test-build (the lockfile-hashed key means they only ever share when the
204+
# dependency tree really is identical, so LWW loss is harmless), but the Turbo
205+
# cache gets its own key: with a shared key, only the last committer's new
206+
# entries survive each run, so the test and build Turbo entries would evict
207+
# each other nondeterministically.
208+
#
209+
# Runner is sized for the COLD-cache build, which is what OOM-killed the 8vcpu
210+
# tier (23 kills / 1074 runs at 98% of its 30.4 GB): warm peaks ~12 GB, cold
211+
# peaked 51 GB. NODE_OPTIONS' --max-old-space-size caps only Node's JS heap,
212+
# not the native Turbopack workers that dominate, so it cannot prevent this.
209213
build:
210214
name: Build App
211215
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu-ubuntu-2404' || 'linux-x64-8-core' }}
@@ -236,7 +240,7 @@ jobs:
236240
uses: ./.github/actions/cache-mount
237241
with:
238242
provider: ${{ vars.CI_PROVIDER }}
239-
key: ${{ github.repository }}-node-modules-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
243+
key: ${{ github.repository }}-node-modules-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}-${{ hashFiles('bun.lock') }}
240244
path: ./node_modules
241245

242246
- name: Mount Turbo cache
@@ -258,6 +262,21 @@ jobs:
258262
key: ${{ github.repository }}-nextjs-cache-${{ github.event_name }}${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
259263
path: ./apps/sim/.next/cache
260264

265+
# Running out of RAM kills the whole VM and surfaces only as "the runner
266+
# has received a shutdown signal" — no mention of memory, ~12 min in. Warn
267+
# with the real numbers so that failure is a one-line diagnosis instead of
268+
# a mystery. Warn, never fail: a warm build peaks ~12 GB and a partial one
269+
# ~28 GB, so a 32 GB runner still completes plenty of builds, and the
270+
# GitHub fallback is the break-glass path — degrading it to a guaranteed
271+
# failure would be worse than the risk this flags.
272+
- name: Check runner memory headroom
273+
run: |
274+
TOTAL_GB=$(awk '/MemTotal/ {printf "%d", $2/1048576}' /proc/meminfo)
275+
echo "Runner memory: ${TOTAL_GB} GB"
276+
if [ "$TOTAL_GB" -lt 40 ]; then
277+
echo "::warning::Runner has ${TOTAL_GB} GB. A cold-cache build peaks ~51 GB, so this run may be OOM-killed (reported only as 'the runner has received a shutdown signal'). Warm/partial builds should still fit."
278+
fi
279+
261280
- name: Install dependencies
262281
run: bun install --frozen-lockfile
263282

bun.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bunfig.toml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ minimumReleaseAge = 604800
77
# dev builds, so every version is structurally younger than any age gate.
88
# The exactly pinned Pi 0.80.10 packages were vetted for the cloud-review SDK
99
# migration; they age out of the gate on 2026-07-24 — drop these four entries then.
10-
# next@16.2.11 and @next/env@16.2.11 are the official Vercel security patch for the
11-
# July 2026 advisories (SSRF, cache confusion, DoS, middleware bypass — GHSA-89xv-2m56-2m9x
12-
# et al.); published 2026-07-21, they age out of the gate on 2026-07-28 — drop these two
13-
# entries then.
10+
# next@16.2.11, @next/env@16.2.11 and the @next/swc-* binaries are the official Vercel
11+
# security patch for the July 2026 advisories (SSRF, cache confusion, DoS, middleware
12+
# bypass — GHSA-89xv-2m56-2m9x et al.); published 2026-07-21, they age out of the gate on
13+
# 2026-07-28 — drop these entries then. The swc binaries ship in lockstep with next and
14+
# MUST be excluded alongside it: they are next's platform-gated optionalDependencies, so
15+
# gating them out leaves them absent from bun.lock entirely, and `bun install
16+
# --frozen-lockfile` then installs no compiler at all — next falls back to downloading one
17+
# at build time, which fails in CI.
1418
# @anthropic-ai/sdk is exactly pinned to 0.114.0, vetted for the agent-events
1519
# streaming work (adaptive thinking display types + transform-json-schema);
1620
# published 2026-07-23, it ages out of the gate on 2026-07-30 — drop this entry then.
@@ -22,6 +26,10 @@ minimumReleaseAgeExcludes = [
2226
"@earendil-works/pi-tui",
2327
"next",
2428
"@next/env",
29+
"@next/swc-darwin-arm64",
30+
"@next/swc-darwin-x64",
31+
"@next/swc-linux-arm64-gnu",
32+
"@next/swc-linux-x64-gnu",
2533
"@anthropic-ai/sdk",
2634
]
2735

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@
7373
"mermaid": "11.15.0",
7474
"zod": "4.3.6"
7575
},
76+
"optionalDependencies": {
77+
"@next/swc-darwin-arm64": "16.2.11",
78+
"@next/swc-darwin-x64": "16.2.11",
79+
"@next/swc-linux-arm64-gnu": "16.2.11",
80+
"@next/swc-linux-x64-gnu": "16.2.11"
81+
},
7682
"devDependencies": {
7783
"@biomejs/biome": "2.0.0-beta.5",
7884
"@octokit/rest": "^21.0.0",

0 commit comments

Comments
 (0)