npm / npm/rfcs

[RRFC] Add --prefer-direct-exec flag for proper signal handling in npm run

Open
#829 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
777
Forks
267
PR merge metrics
No merged PRs in 30d

Description

Motivation ("The Why")

npm run spawns scripts through a shell (/bin/sh -c), creating an unnecessary intermediate process that interferes with signal propagation. This causes Node.js applications to terminate abruptly without completing their cleanup procedures when receiving SIGTERM/SIGINT signals.

This is a fundamental Unix process management issue that affects:

  • Docker containers (not just Kubernetes)
  • Systemd services
  • Process managers (PM2, Forever, etc.)
  • CI/CD environments
  • Any production deployment using npm run

The problem: When a termination signal is sent, the shell may exit immediately without waiting for its child process, causing npm to exit prematurely and killing the Node.js application before graceful shutdown completes.

Example

Consider a basic Node.js server with cleanup logic:

// server.js
const server = require('http').createServer();

process.on('SIGTERM', () => {
  console.log('Graceful shutdown started');
  server.close(() => {
    console.log('Cleanup complete');
    process.exit(0);
  });
});

server.listen(3000);

Problem occurs in any of these scenarios:

  1. Docker container: docker stop <container>
  2. Systemd service: systemctl stop myapp
  3. Process manager: pm2 stop app
  4. Manual termination: kill <pid>

In all cases, if started with npm run start, the graceful shutdown may not complete because the shell intermediary disrupts signal propagation.

How

Add an opt-in flag to control how npm run spawns processes, eliminating the problematic shell intermediary when not needed.

Current Behaviour
# Current process tree with npm run
npm → /bin/sh -c "node server.js" → node server.js
 ↓            ↓
SIGTERM   (shell may exit without waiting)
 ↓
(npm exits because child is gone)
(node process terminated abruptly)

The shell (/bin/sh) behavior varies:

  • Some shells (ash, dash) exit immediately on SIGTERM
  • This causes npm to detect child exit and terminate
  • Node.js process is killed before cleanup completes

Current workaround requires modifying code:

{
  "scripts": {
    "start": "exec node server.js"  // Must add exec manually
  }
}

This workaround is:

  • Not documented clearly
  • Not obvious to developers
  • Required in every project
  • Easy to forget
Desired Behaviour

Provide an opt-in mechanism that works without code changes:

Option 1 - Environment variable (for deployments):

NPM_PREFER_DIRECT_EXEC=1 npm run start

Option 2 - CLI flag (for specific runs):

npm run start --prefer-direct-exec

Option 3 - Config file (for projects):

# .npmrc
prefer-direct-exec=true

When enabled:

  • Simple commands: Spawn directly without shell
  • Complex commands (with pipes, &&, etc.): Auto-prepend exec to replace shell
  • Windows: No change (different process model)
  • Default: Current behavior (backward compatible)

Result:

# With flag enabled
npm → node server.js  (direct, no shell)
 ↓        ↓
SIGTERM  (node receives signal)
 ↓        ↓
(waits) (graceful shutdown completes)
References
  • Related to npm/run-script#237 (implementation tracking)
  • Related to npm/cli#8509 (original feature request)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the npm run entry point and review the related npm/run-script#237 and npm/cli#8509 discussions. Compare the proposed CLI flag, environment variable, and .npmrc options, including shell commands and Windows behavior; done means an agreed design that preserves current defaults while enabling direct execution and correct signal handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, shell
Domain
cli, developer-experience
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.