Avoid process.exit

ID

javascript.avoid_process_exit

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Reliability

Language

JavaScript

Tags

best-practice, reliability

Description

Reports calls to process.exit(…​). Node.js’s process.exit terminates the process synchronously — pending I/O (logs, file writes, network flushes), finally cleanup, and graceful-shutdown handlers all stop running. Library code in particular must never call process.exit: it removes the host application’s chance to handle the error gracefully.

Rationale

  • Open file descriptors, in-flight HTTP requests, and pino/winston buffered logs are silently dropped.

  • Any registered process.on("exit", …​) handler still runs but with no time for asynchronous work.

  • Tests that import the module crash unless they monkey-patch process.exit or wrap with subprocesses — a major test-suite headache.

  • The exit code becomes implicit, scattered across the codebase, and hard to audit.

// Bad - drops pending log writes, breaks graceful shutdown
if (configBroken) {
  console.error("config invalid");
  process.exit(1);
}

Remediation

Throw an error and let the top-level handler decide the exit code, or set a non-zero process.exitCode and return from main so the runtime exits cleanly after pending I/O drains.

// Good - clean shutdown
if (configBroken) {
  throw new ConfigError("config invalid");
}

// Or, in the entry-point only:
async function main() {
  try {
    await run();
  } catch (err) {
    log.error(err);
    process.exitCode = 1;
  }
}