TurnSignal

Playwright exit code 137 in CI: out of memory, and the tests you lost

By · October 16, 2026 · 4 min read

In short

  • Exit code 137 is 128 + 9: the process was killed with SIGKILL. In CI and containers the usual sender is the out-of-memory killer.
  • When the step itself ends with 137, the Playwright runner died, not only a worker. It wrote no report, so the tests it had not finished are simply absent from the results.
  • Confirm it is memory by halving workers for one run. Then fix the cause: fewer workers per machine, more shards, --ipc=host in Docker, or a leaking test.
  • Before the next crash, make sure results survive it: write them as tests finish and upload them from a step that always runs.

Where you see 137

On GitHub Actions the step fails with Error: Process completed with exit code 137. Other CI systems print the same number in their own words, and Kubernetes reports the container as OOMKilled with exit code 137. The shell adds 128 to the signal number when a process is killed by a signal, and 9 is SIGKILL, a signal the process cannot catch or clean up after.

If the log instead shows worker process exited unexpectedly (code=null, signal=SIGKILL) and the step finishes normally, only one worker died and Playwright recovered. That case has its own guide: worker process exited unexpectedly.

What 137 does to your results

A killed runner never reaches the end of the run. The HTML report, the JUnit file and the blob report for sharding are written at the end, so none of them is written. The tests that had already finished are lost with the process unless something saved them earlier, and the tests that had not started are not marked as failed or skipped: they are just not there.

With sharding this is easy to miss. The killed shard fails its job, someone re-runs it, it passes, and the merged report looks complete. When the job is allowed to fail (continue-on-error) or the merge step only reads the reports that exist, the run can even look green with a quarter of the suite missing. The guide to missing tests after a crashed shard shows how to count what should have reported.

Confirm it was memory

  1. Re-run once with half the workers (npx playwright test --workers=2, or workers in the config). If 137 goes away, memory was the cause.
  2. Look at the runner size. Each worker starts its own browser, so memory grows with workers; a machine with less memory than your laptop needs fewer workers than your laptop.
  3. In Kubernetes, kubectl describe pod shows Reason: OOMKilled. On a VM you control, the kernel log (dmesg) names the killed process.
  4. If the job was cancelled or hit its time limit instead, the log says so and the process usually gets SIGTERM first; that is a timeout problem, not a memory one.

What to change first

  • Fewer workers per machine. workers: process.env.CI ? 2 : undefined is a common starting point; raise it until memory gets tight.
  • More shards. --shard=1/4 … --shard=4/4 spreads the suite over more machines, so each holds fewer browsers.
  • Docker: `--ipc=host`. Playwright's Docker guide warns that Chromium can run out of memory without it, and recommends --init as well.
  • A leaking test. Contexts or pages opened in a loop and never closed, or large downloads kept in memory, grow until something is killed. Run a suspect file with --workers=1 --repeat-each=10 and watch memory.
  • A bigger runner, when the suite simply needs more memory than the machine has.
  • Video and trace settings. Recording video or a trace for every test costs memory and disk on each worker. retain-on-failure or on-first-retry keeps the evidence for failures without paying for every passing test.
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  fullyParallel: true,
  workers: process.env.CI ? 2 : undefined,
});

Keep the results the next crash would lose

Fixing memory makes 137 rarer; it does not make a killed runner report its tests. For that, results have to leave the process as tests finish, and something that survives the kill has to send them:

  • A reporter that writes each result to disk when the test ends, rather than one file at the end of the run.
  • An upload step with if: always() on GitHub Actions (or the equivalent "always run" setting elsewhere), so it runs after the test step fails.
  • In containers and pods, whose disk goes away with them, a small parent process that runs the tests and uploads before the container exits.

TurnSignal for Playwright does these three things: its reporter writes results to disk in CI as tests finish and sends them while the run is going, npx turnsignal upload in an always-run step sends whatever was left, and npx turnsignal run -- npx playwright test uploads from outside the test process. Tests a killed shard never reported are listed as Missing, with the lowest free memory seen on that machine. Your exit code stays Playwright's. Setup is in the docs.

Questions people ask

Is exit code 137 always out of memory?

No. It only says the process got SIGKILL. Out of memory is the usual sender in CI, but a runner being shut down or a manual kill -9 produce the same code. Halving the workers for one run tells you quickly.

Does raising Node's heap with --max-old-space-size help?

Rarely. Most of the memory belongs to the browsers, which are separate processes outside Node's heap. A larger Node heap can even make the machine run out sooner.

Will retries help?

Not when the whole runner is killed: retries happen inside the Playwright process, and that process is gone. They only help when a single worker dies and the runner survives.

Sources

Playwright details were checked against Playwright 1.63 and its documentation on October 16, 2026.

Try TurnSignal on your next run

Add one reporter line next to your existing reporters. Free to start, no credit card, no repository access.

Get started free Read the docs