The child-process module
Reference for node:child_process in Cruft: the synchronous lane (execSync, spawnSync, execFileSync) and the asynchronous lane (spawn, exec, execFile, fork), which options are honored and which are silently ignored. All spawning is capability-gated and denied under sealed mode.
Focused-tier module. Both lanes work: the synchronous functions (execSync, spawnSync, execFileSync) and the asynchronous surface (spawn, exec, execFile, fork). Async spawn delivers stdout data and close events with correct exit codes, and exec invokes its callback with output. The real caveats are at the options level: several common options are silently ignored (see the defects section). All spawning is capability-gated.
Sync-lane options
The synchronous lanes (execSync, spawnSync, execFileSync) honor env and input, and return decoded strings when you pass encoding. spawnSync("printenv", ["V"], { env: { V: "x" } }) sees the supplied variable, and { input: "…" } pipes to the child's stdin. The timeout option is enforced: a child past the budget is terminated and result.error reports spawnSync ETIMEDOUT.
Import forms
const cp = require("node:child_process"); // or "child_process"
import cp from "node:child_process";
Synchronous API
| Export | Signature | Notes |
|---|---|---|
execSync | execSync(command[, options]) -> Buffer | Runs through the shell; throws Command failed: … (exit N) with stderr on non-zero exit. Returns a Buffer by default (Node-faithful); pass { encoding: "utf8" } to get a decoded string. Honored option: cwd. |
spawnSync | spawnSync(command[, args][, options]) -> result | Direct exec, no shell. Never throws: a spawn failure lands in result.error. Result: { status, stdout, stderr, pid, signal, error? }, stdout/stderr are strings, pid is 0, signal is null. Honored options: cwd, timeout (ETIMEDOUT in result.error). |
execFileSync | execFileSync(file[, args][, options]) | Direct-exec sync variant. |
Example:
$ cruft -e 'const cp = require("node:child_process");
console.log(cp.execSync("echo hello").toString().trim());
const r = cp.spawnSync("echo", ["a", "b"]);
console.log(r.status, JSON.stringify(r.stdout));'
hello
0 "a b\n"
Asynchronous API
spawn, exec, execFile, fork, and the promisified exec hook are installed with the Node shapes and the core paths function:
| Export | Behavior |
|---|---|
spawn(cmd[, args][, opts]) -> ChildProcess | .stdout/.stderr emit 'data'; 'close'/'exit' fire with the exit code. |
exec(cmd[, opts], cb) | Callback invoked with (err, stdout, stderr); non-zero exit produces an error argument. |
execFile(file[, args][, opts], cb) | Direct-exec async variant. |
fork(modulePath[, args][, opts]) | Present; spawns with 'message' IPC shapes and a _forkChild internal. silent: true does not project child output through child.stdout (output is inherited). |
Option handling on the async lanes is not exhaustive; treat option support as conservative (cwd-level).
Capability gating
Spawning is the most dangerous host authority and flows through the capability dispatcher. Default posture runs; --audit logs each spawn (naming the program); --sealed denies:
$ cruft --sealed -e 'require("node:child_process").execSync("echo x")'
cruft: evaluation error: Thrown: TypeError: process.spawn(echo x): no process
capability granted to module '…' (mode: sealed) — hint: add to caps in
package.json: { process: { may_spawn: true } }
Not implemented / gaps
execSyncreturns a Buffer by default (string only with{ encoding });spawnSyncresults carry stringstdout/stderr,pid: 0,signal: null(the two sync lanes decode differently).forkwithsilent: truedoes not capture child output.- Anything not listed above (
detached,stdioarrays,AbortSignalsupport) is not present.