Skip to content

child_process.exec{,File} don't allow setting stdin to ignore leading to hangs #60077

@AKarbas

Description

@AKarbas

Version

v22.19.0

Platform

Darwin <hostname> 25.0.0 Darwin Kernel Version 25.0.0: Wed Sep 17 21:41:50 PDT 2025; root:xnu-12377.1.9~141/RELEASE_ARM64_T6030 arm64

Subsystem

child_process

What steps will reproduce the bug?

Install ripgrep (https://github.com/BurntSushi/ripgrep) — just a example of something that breaks this way.
run the following and it will time out:

const { execFile } = await import("node:child_process")
const child = execFile("rg", ["foo"], {
  stdio: ["ignore", "pipe", "pipe"],
  timeout: 1_000
}, (error, _stdout, _stderr) => {
  console.log("After one second:", error)
})

You will see:

❯ node
Welcome to Node.js v22.19.0.
Type ".help" for more information.
> const { execFile } = await import("node:child_process")
undefined
> const child = execFile("rg", ["foo"], {
...   stdio: ["ignore", "pipe", "pipe"],
...   timeout: 1_000
... }, (error, _stdout, _stderr) => {
...   console.log("After one second:", error)
... })
undefined
> After one second: Error: Command failed: rg foo

    at genericNodeError (node:internal/errors:983:15)
    at wrappedFn (node:internal/errors:537:14)
    at ChildProcess.exithandler (node:child_process:417:12)
    at ChildProcess.emit (node:events:519:28)
    at ChildProcess.emit (node:domain:552:15)
    at maybeClose (node:internal/child_process:1101:16)
    at ChildProcess._handle.onexit (node:internal/child_process:304:5)
    at Process.callbackTrampoline (node:internal/async_hooks:130:17) {
  code: null,
  killed: true,
  signal: 'SIGTERM',
  cmd: 'rg foo'
}

How often does it reproduce? Is there a required condition?

With any command (like rg) that could hang on an open stdin. See: BurntSushi/ripgrep#2056

What is the expected behavior? Why is that the expected behavior?

For exec and execFile to allow setting stdin to ignore — setting stdout and stderr is irrelevant as they are gathered by exec{,File} itself. The current implementation simply has no way to set this:

node/lib/child_process.js

Lines 301 to 326 in 31c6e70

/**
* Spawns the specified file as a shell.
* @param {string} file
* @param {string[]} [args]
* @param {{
* cwd?: string | URL;
* env?: Record<string, string>;
* encoding?: string;
* timeout?: number;
* maxBuffer?: number;
* killSignal?: string | number;
* uid?: number;
* gid?: number;
* windowsHide?: boolean;
* windowsVerbatimArguments?: boolean;
* shell?: boolean | string;
* signal?: AbortSignal;
* }} [options]
* @param {(
* error?: Error,
* stdout?: string | Buffer,
* stderr?: string | Buffer
* ) => any} [callback]
* @returns {ChildProcess}
*/
function execFile(file, args, options, callback) {

What do you see instead?

Simply hangs.

Additional information

Shout out to my teammate @Kaushal1011 who figured out why this got stuck!

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