WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

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

@seungwanHam

Description

@seungwanHam

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions