Tuning tman for Jest
Open handles keep the process alive forever — the exact shape stall detection catches.
Recommended starting caps
| cap | start at | what it answers |
|---|---|---|
stall | 10m | hang backstop — no output and no CPU/IO activity for this long |
max-time | 20m | wall-clock budget — the only cap that bounds a run that is working but will never finish |
max-mem | 4096 | RSS ceiling across the process tree |
max-parallel | 2 | slots per name-and-directory bucket |
These are starting points derived from how Jest behaves, not measurements of your machine. Every one of them should be replaced by a number you took from your own runs — the last section on this page is how.
alias "test" {
command "npx"
args "jest" "--ci"
max-time "20m"
max-mem 4096
}Jest is the clearest illustration of why a stall backstop is not the same thing as a timeout. Its signature failure is a suite that passes, prints its summary, and then never exits, because an open socket or timer is holding the event loop. The process is silent and idle — no output, no CPU, no IO — which is exactly the signature tman kills on, at exit 125.
A
max-timewould also end that run, but it ends every long run the same way and tells you nothing. Exit 125 says this process was doing nothing, which points straight at the handle. Runjest --detectOpenHandlesonce after a 125 and it usually names the culprit.
Workers are the memory multiplier
Jest spawns a worker per core by default, each a full Node process with its own module registry. On a 16-core machine that is 16 heaps, and a module-level cache that grows across test files grows in all of them at once. max-mem 4096 catches that; --maxWorkers=50% in the alias args stops it happening.
The ceiling is summed across the process tree on Linux, which is what makes it useful here — measuring only the parent would miss every worker.
`--ci` belongs in the alias
Under an agent there is no one to answer an interactive prompt, and a snapshot written rather than compared is a test that passed by rewriting its own expectation. --ci makes an unmatched snapshot fail instead of being silently updated. That is the same principle as tman's own scaffolding: nothing should report success without doing the work.
Turn the starting point into a real number
Everything above is derived from how this tool behaves, not from your machine. The numbers that matter come from your own runs, and getting them takes one command:
tman status --json | jq '.runs[] | {name, exit, duration_ms}'Set max-time from the slowest successful run you are willing to wait for, with real headroom — a cap that fires on healthy work teaches everyone to raise the cap, and after a few rounds of that it bounds nothing. Set stall from the longest stretch the command legitimately produces no output and no CPU, then double it.
Exit 124, 125, and 126 are findings, not flakes. 124 is a wall-clock timeout, 125 a stall, 126 a resource cull. Widening the cap that fired is the right response only after you know why it fired — the first two times a suite trips
max-mem, the interesting question is what is holding the memory.
Frequently asked
What does exit 125 mean for a Jest run?
A stall: no output and no CPU, IO, or kernel IO-wait activity for the stall window. For Jest that most often means the suite finished but an open handle — a socket, a timer, a database pool — is keeping the event loop alive. `jest --detectOpenHandles` will usually name it.
Should I set max-mem for Jest?
Yes, around 4096 MB to start. Jest runs one worker process per core by default, so a per-file leak is multiplied by the worker count. Pair it with `--maxWorkers=50%`, which reduces the multiplier rather than just catching the result.