Node.js ERR_WORKER_OUT_OF_MEMORY - Fix Worker Terminated by Memory Limit
ERR_WORKER_OUT_OF_MEMORY is the specific error a parent thread receives when a Worker exceeds the heap ceiling set by its own resourceLimits (or the implicit default). It is distinct from a plain heap crash because the limit it hit was configured on the worker, not via NODE_OPTIONS.
What this error means
The main thread catches an error event with code ERR_WORKER_OUT_OF_MEMORY and a message that the worker was terminated for reaching its memory limit. The parent process itself never crashed - a resourceLimits.maxOldGenerationSizeMb you set (or the default) was too small for the worker’s workload.
Error [ERR_WORKER_OUT_OF_MEMORY]: Worker terminated due to reaching
memory limit: JS heap out of memory
at [kOnExit] (node:internal/worker)
at Worker.<computed>.onexit (node:internal/worker)
code: 'ERR_WORKER_OUT_OF_MEMORY'Diagnose it: what is different about the runner?
A build that passes locally and fails on a runner differs in a small number of predictable ways. Check those before changing build configuration, because the build config is usually not the thing that changed.
- run: |
node --version && npm --version
echo "NODE_ENV=$NODE_ENV CI=$CI"
nproc && free -h && df -h /
ls -la node_modules/.bin | headCommon causes
resourceLimits set too low for the worker
A Worker created with a small maxOldGenerationSizeMb hits that ceiling and is terminated, surfacing this code to the parent - even though the parent had plenty of headroom.
The implicit per-worker default is too small
Without explicit resourceLimits, a worker still has a finite heap. A memory-heavy task (parsing, image work, large buffers) can exceed it.
How to fix it
Raise the worker’s resourceLimits
Give the Worker an explicit, larger old-generation ceiling.
const { Worker } = require('node:worker_threads')
new Worker('./task.js', {
resourceLimits: { maxOldGenerationSizeMb: 2048 },
})Shrink the worker’s footprint
- Stream or chunk large inputs instead of materializing them in the worker heap.
- Lower the number of workers running at once.
- Move the limit into the worker’s own
execArgv(--max-old-space-size) if you spawn it that way.
The three that account for most of them
- Case sensitivity. Linux runners are case sensitive, macOS is not. An import with the wrong case resolves locally and fails in CI.
- Out of memory. Exit code 137 is a SIGKILL from the kernel, not a build error. Raise
--max-old-space-sizeor use a larger runner. - devDependencies pruned.
NODE_ENV=productionmakesnpm ciskip devDependencies, so the build tool itself goes missing. Set it after install, not before.
How to prevent it
- Set worker
resourceLimitsdeliberately, sized to the runner. - Cap worker-pool concurrency in CI.
- Process large data incrementally inside workers.