The npm installer's wrapper exits 0 when the binary it runs is killed by a signal.
Package.run in cargo-dist/templates/installer/npm/binary-install.js ends with:
const result = spawnSync(binPath, args, options);
if (result.error) {
error(result.error);
}
process.exit(result.status);
When the child is terminated by a signal (for example the OOM killer, or SIGKILL), spawnSync returns status: null and signal: 'SIGKILL'. process.exit(null) exits with code 0, so the npm wrapper reports success for a binary that never finished.
Minimal reproduction (Node 20.20.2, same logic as the template):
const { spawnSync } = require('child_process');
const result = spawnSync('sh', ['-c', 'kill -9 $$'], { stdio: 'inherit' });
console.error(`status=${result.status} signal=${result.signal}`); // status=null signal=SIGKILL
process.exit(result.status); // wrapper exits 0
We hit this with @posthog/cli, which ships this installer (checked 0.16.2 and 0.18.9). A build step runs the CLI to upload source maps and treats exit code 0 as "uploaded". A killed upload therefore looks successful to CI.
Suggested fix: treat a signal as failure, e.g.
if (result.signal) {
error(`${binaryName} was terminated by ${result.signal}`);
}
process.exit(result.status);
(error() already exits 1.) Alternatively use process.exit(result.status ?? 1), or re-raise the same signal on the wrapper with process.kill(process.pid, result.signal) so callers see the original signal.
The npm installer's wrapper exits 0 when the binary it runs is killed by a signal.
Package.runincargo-dist/templates/installer/npm/binary-install.jsends with:When the child is terminated by a signal (for example the OOM killer, or
SIGKILL),spawnSyncreturnsstatus: nullandsignal: 'SIGKILL'.process.exit(null)exits with code 0, so the npm wrapper reports success for a binary that never finished.Minimal reproduction (Node 20.20.2, same logic as the template):
We hit this with
@posthog/cli, which ships this installer (checked 0.16.2 and 0.18.9). A build step runs the CLI to upload source maps and treats exit code 0 as "uploaded". A killed upload therefore looks successful to CI.Suggested fix: treat a signal as failure, e.g.
(
error()already exits 1.) Alternatively useprocess.exit(result.status ?? 1), or re-raise the same signal on the wrapper withprocess.kill(process.pid, result.signal)so callers see the original signal.