Skip to content

pnpm test is OOM-killed at turbo's default concurrency on a 4-CPU/15 GB container — exit 137, 16 of 91 tasks never run #11938

Description

@os-steve

Found while taking the machine-lane baseline for the QA wave (#9296, round #9298). Filed as a finding, not fixed — R0 lands no code.

Summary

pnpm test (i.e. turbo run test with no --concurrency) is killed by the kernel cgroup OOM killer on this container. It does not report a failing test — it dies. Reproduced twice; each run was killed at a different package, which is the signature of a resource kill rather than a package defect.

turbo.json declares no concurrency, so turbo defaults to 10 parallel tasks. This box is 4 CPU / ~15 GB. Ten concurrent test tasks, each forking its own vitest worker pool, exceeds the memory allowance.

Environment

  • framework sha: 1f6d04703df8f518c80d0fbaa8b19262068e405d (tip of main at run time)
  • .objectui-sha: 190fbd01d0615e2e168faf9e08b8ad7844bc039d
  • node v22.22.2, pnpm 10.31.0, linux x64, nproc=4, 15 GB RAM
  • fresh git worktree add --detach + a single pnpm install; deps prebuilt; run serialized under the shared verify lock (no other seat's gate running)

Reproduction

pnpm test

Run 1 — exit 137, 7m48s:

@objectstack/service-storage#test: ERROR command (…/packages/services/service-storage) pnpm run test exited (137)
Tasks: 75 successful, 91 total
Cached: 45 cached, 91 total
Time: 7m47.442s
Failed: @objectstack/service-storage#test
ERROR run failed: command exited (137)

Run 2 — exit 137, 6m57s, killed at a different package:

@objectstack/metadata-protocol#test: ERROR command (…/packages/metadata-protocol) pnpm run test exited (137)
Tasks: 75 cached, 91 total
Time: 6m57.19s
Failed: @objectstack/metadata-protocol#test

Run 1 carried NODE_OPTIONS=--max-old-space-size=4096; run 2 was run plain, with no NODE_OPTIONS at all, and died the same way. The heap flag is not the cause.

Kernel confirmation (dmesg), two events, one per run:

Memory cgroup out of memory: Killed process 28332 (node) total-vm:45803420kB, anon-rss:820896kB, …

No test actually failed

This is the part that matters for reading the gate. Across both runs, zero vitest summaries reported a failing test:

run 1run 2
vitest summaries emitted3131
tests passed8 9148 914
tests failed00
turbo tasks successful75 / 9175 / 91

The eight packages that logged ELIFECYCLE Test failed (objectql, spec, driver-sql, plugin-sharing, metadata-protocol, service-analytics, plugin-audit, service-storage in run 1) emitted no vitest failure summary — they were torn down by the kill, not reddened by an assertion. Read naively, the gate looks like eight broken packages; it is one OOM.

16 of 91 tasks never ran at all. A run that ends this way covers an unknown subset of the repo, so it cannot be reported as a pass or as a product failure.

What does work

Cutting the fan-out completes the suite:

pnpm exec turbo run test --continue --concurrency=2
Tasks: 135 successful, 136 total
Time: 30m38.998s

Peak memory during that run stayed around 7.6 GB used, versus the OOM at default fan-out.

Relationship to prior art

Same class as #9311 (closed, fixed) — a pnpm test at default concurrency that reds for reasons unrelated to the code under test — but a different mechanism: #9311 was two default-vitest-timeout cases losing a CPU race, and was correctly fixed by giving those cases real timeouts. This one is memory, not time, and no per-test timeout can address it. #9311 explicitly left the broader question open:

Separately worth a maintainer decision: whether turbo.json should pin a concurrency proportional to available CPUs, so the whole suite stops depending on runner size.

This issue is that question arriving with a measurement behind it. See also #4845 (closed) for the precedent that this repo's memory ceiling is a real constraint, not a theoretical one.

Suggested direction (not implemented here)

Decide whether pnpm test should be runnable on a small box at all, and if so how:

  • pin concurrency in turbo.json (or in the test script) proportional to nproc, so the suite stops depending on runner size; or
  • leave the default and document --concurrency=2 as the supported invocation on constrained runners, so agents and contributors stop reading an OOM as eight broken packages.

The first option is a repo-wide behaviour change and is a maintainer call, which is why nothing is changed here.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions