tests/conformance/run.js runs the same ecosystem file under PM2 and under
Vexor, each in an isolated home, and compares what a probe app experienced
(runs, argv, cwd, environment, instance numbers, stop signal) with what each
tool reported (status, restart count). Run it in a throwaway container with
pm2 and vexor on PATH:
docker run --rm -v "$PWD/dist":/bin2 -v "$PWD/tests/conformance":/c:ro node:22-alpine \
sh -c 'npm i -g pm2@7.1.0 && cp /bin2/vexor /usr/local/bin/ && node /c/run.js --out /bin2/report.md'Latest run: PM2 7.1.0 vs Vexor 0.1.1, 11 scenarios, 10 identical, 1 intentional difference.
The harness found two real incompatibilities in 0.1.0, both fixed for 0.1.1:
max_restarts: Nallowed N + 1 runs before errored; PM2 allows N.max_restarts: 0now means PM2's default, 16.- Graceful stop sent SIGTERM; PM2 sends SIGINT. Vexor now sends SIGINT
by default, and the new
kill_signalsetting chooses another signal.
When listen_timeout expires and the app never called process.send('ready'),
PM2 marks the app online anyway. Vexor treats it as a failed start
(stopped, counted against max_restarts, restarted with backoff; errored when
the budget runs out), so a broken app is never reported healthy and a reload
never retires a working process in favour of one that never became ready.
Vexor limits automatic restarts to one per second per instance, while PM2
restarts immediately with restart_delay: 0. The final counts match (see
crash-budget-0), but Vexor reaches them more slowly; the harness waits long
enough for both.
Generated by tests/conformance/run.js: PM2 7.1.0 vs vexor 0.1.1, Node v22.23.4, linux/arm64, 2026-10-23.
Each scenario runs the same ecosystem file under both tools. Rows show what the probe app saw and what each tool reported; bold rows differ.
How many runs before errored with max_restarts 3?
| PM2 | Vexor | |
|---|---|---|
| runs | 3 |
3 |
| statuses | ["errored"] |
["errored"] |
| restarts | [2] |
[2] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | – | – |
How many runs before errored with max_restarts 0?
| PM2 | Vexor | |
|---|---|---|
| runs | 16 |
16 |
| statuses | ["errored"] |
["errored"] |
| restarts | [15] |
[15] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | – | – |
How many runs before errored with max_restarts 1?
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["errored"] |
["errored"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | – | – |
vexor said: crash-budget-1 0 errored - - 0
Relative cwd and script resolution
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["online"] |
["online"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "sub" |
"sub" |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | "SIGINT" |
"SIGINT" |
env + env_production with --env production; args string splitting; instance var
| PM2 | Vexor | |
|---|---|---|
| runs | 2 |
2 |
| statuses | ["online","online"] |
["online","online"] |
| restarts | [0,0] |
[0,0] |
| argv | ["x","y z","w"] |
["x","y z","w"] |
| cwd | "." |
"." |
| mode | "prod" |
"prod" |
| a | "1" |
"1" |
| insts | ["0","1"] |
["0","1"] |
| signal | "SIGINT" |
"SIGINT" |
Exit 0 with stop_exit_codes [0]
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["stopped"] |
["stopped"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | – | – |
Crash with autorestart false
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["stopped"] |
["stopped"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | – | – |
Which signal does stop send?
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["stopped"] |
["stopped"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | "SIGINT" |
"SIGINT" |
wait_ready with no ready signal (listen_timeout 1500)
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["online"] |
["errored"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | "SIGINT" |
"SIGINT" |
wait_ready with a ready signal
| PM2 | Vexor | |
|---|---|---|
| runs | 1 |
1 |
| statuses | ["online"] |
["online"] |
| restarts | [0] |
[0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0"] |
["0"] |
| signal | "SIGINT" |
"SIGINT" |
Cluster mode with 2 instances: instance numbers
| PM2 | Vexor | |
|---|---|---|
| runs | 2 |
2 |
| statuses | ["online","online"] |
["online","online"] |
| restarts | [0,0] |
[0,0] |
| argv | [] |
[] |
| cwd | "." |
"." |
| mode | – | – |
| a | – | – |
| insts | ["0","1"] |
["0","1"] |
| signal | "SIGINT" |
"SIGINT" |