ci: build the web app in CI, on the runner with memory for it #576

Merged
gmackie merged 2 commits from ci/web-build-job into main 2026-09-14 05:54:31 +00:00
Owner

Nothing in CI compiled apps/web for production. CI runs typecheck, lint, unit tests and the Storybook build — and all of them passed on #564, whose next build couldn't resolve its own imports. Main then sat undeployable until the deploy itself hit the error (fixed in #575).

This adds a web-build job that runs pnpm --filter @forgegraph/web build. That's the same script the deploy runs: it copies the tree-sitter wasms, then runs next build --webpack.

Why it runs on heavy, not hetzner-worker

I measured the build locally at 4d71a6a8's fix (fresh install):

heap cap result peak RSS
1792 MB (this workflow's NODE_OPTIONS) JavaScript heap out of memory 2.88 GB before dying
3072 MB passes, 216 pages 3.37 GB

hetzner-worker runs each job in a 3 GB container (--memory 3g), so the passing build would be OOM-killed there. hetzner-bob (heavy) gives each job 5 GB on a 15.6 GB box with ~11.5 GB free. The step overrides NODE_OPTIONS to 3072; every other job and the workflow-wide cap are unchanged.

It's a separate job for the same reason storybook is: stacking a production build on the ci job's typecheck is what pushes a small runner into swap.

Trade-offs

  • Adds a few minutes of runner time per PR and push.
  • Runs on hetzner-bob, whose disk is at 86% and has filled before and broken CI's database tests. This job needs no database and its output is cleaned per run, so the extra disk pressure is small, but it's worth watching.

Correction to the original description: it said this moves a job onto hetzner-bob, "which ForgeGraph CI otherwise avoids". That was wrong. hetzner-bob also advertises forgegraph-worker, so the existing gitleaks, storybook and ci jobs (runs-on: [forgegraph-ci, forgegraph-worker]) can already land there. The header comment in ci.yml claiming forgegraph-worker restricts jobs to hetzner-worker is stale for the same reason. This job's routing is still right: heavy exists only on hetzner-bob, which is what guarantees the 5 GB container.

This PR's own CI run is the first real test of the job.

🤖 Generated with Claude Code

Nothing in CI compiled `apps/web` for production. CI runs typecheck, lint, unit tests and the Storybook build — and all of them passed on #564, whose `next build` couldn't resolve its own imports. Main then sat undeployable until the deploy itself hit the error (fixed in #575). This adds a `web-build` job that runs `pnpm --filter @forgegraph/web build`. That's the same script the deploy runs: it copies the tree-sitter wasms, then runs `next build --webpack`. ## Why it runs on `heavy`, not hetzner-worker I measured the build locally at `4d71a6a8`'s fix (fresh install): | heap cap | result | peak RSS | |---|---|---| | 1792 MB (this workflow's `NODE_OPTIONS`) | **`JavaScript heap out of memory`** | 2.88 GB before dying | | 3072 MB | passes, 216 pages | **3.37 GB** | hetzner-worker runs each job in a **3 GB container** (`--memory 3g`), so the passing build would be OOM-killed there. hetzner-bob (`heavy`) gives each job 5 GB on a 15.6 GB box with ~11.5 GB free. The step overrides `NODE_OPTIONS` to 3072; every other job and the workflow-wide cap are unchanged. It's a separate job for the same reason `storybook` is: stacking a production build on the `ci` job's typecheck is what pushes a small runner into swap. ## Trade-offs - Adds a few minutes of runner time per PR and push. - Runs on hetzner-bob, whose disk is at 86% and has filled before and broken CI's database tests. This job needs no database and its output is cleaned per run, so the extra disk pressure is small, but it's worth watching. **Correction to the original description:** it said this moves a job onto hetzner-bob, "which ForgeGraph CI otherwise avoids". That was wrong. hetzner-bob also advertises `forgegraph-worker`, so the existing `gitleaks`, `storybook` and `ci` jobs (`runs-on: [forgegraph-ci, forgegraph-worker]`) can already land there. The header comment in `ci.yml` claiming `forgegraph-worker` restricts jobs to hetzner-worker is stale for the same reason. This job's routing is still right: `heavy` exists only on hetzner-bob, which is what guarantees the 5 GB container. This PR's own CI run is the first real test of the job. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ci: build the web app in CI, on the runner with memory for it
All checks were successful
CI / gitleaks (pull_request) Successful in 6s
CI / storybook (pull_request) Successful in 2m1s
CI / web-build (pull_request) Successful in 3m24s
forgegraph/ci CI passed
CI / ci (pull_request) Successful in 11m17s
ef6b90e499
Nothing in CI compiled apps/web for production. Typecheck, lint and unit
tests all passed on #564, whose `next build` could not resolve its own
imports, and main sat undeployable until the deploy found it (fixed in
#575). This adds a web-build job that runs the same build script the deploy
runs, so that class of failure turns a PR red instead of main.

It targets the `heavy` runner rather than hetzner-worker because of measured
memory. Under the workflow's 1792 MB heap cap the build dies with
"JavaScript heap out of memory"; at 3072 MB it passes with a 3.37 GB peak
RSS. hetzner-worker runs each job in a 3 GB container and would OOM-kill it;
hetzner-bob gives each job 5 GB on a 15.6 GB box. The step overrides
NODE_OPTIONS to 3072 for that reason, and nothing else in the workflow moves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

Preview environment is live: https://pr-576-forgegraph.forgegraf.com

Deployed ef6b90e4 with the beta stage's environment. It redeploys on every push and is destroyed when this PR closes.

Preview environment is live: https://pr-576-forgegraph.forgegraf.com Deployed `ef6b90e4` with the beta stage's environment. It redeploys on every push and is destroyed when this PR closes.
ci: correct the runner-routing comment
All checks were successful
CI / gitleaks (pull_request) Successful in 7s
CI / storybook (pull_request) Successful in 1m21s
CI / web-build (pull_request) Successful in 3m44s
forgegraph/ci CI passed
CI / ci (pull_request) Successful in 11m33s
58592acadb
The header said forgegraph-worker restricts ForgeGraph CI to hetzner-worker.
It does not: hetzner-bob advertises forgegraph-worker too (checked on the
runner, 2026-09-14), so gitleaks, storybook and ci can land on either box.
Say what the labels actually do, including why web-build asks for `heavy`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gmackie/ForgeGraph!576
No description provided.