This error occurs when spawning a detached child process in Node.js without properly configuring the stdio option. Detached processes require specific stdio settings to run independently from their parent process.
This error is thrown by Node.js when using `spawn()`, `fork()`, or related child process methods with the `detached: true` option but without appropriate stdio configuration. When you spawn a process with `detached: true`, you're telling Node.js to run the child process independently so it can outlive the parent. However, by default, child processes inherit their parent's standard input, output, and error streams. If a detached child inherits these streams, it remains attached to the controlling terminal, preventing true independence. Node.js enforces that detached processes must use stdio options that disconnect them from the parent's terminal: either `'ignore'` to discard all I/O, or specific file descriptors (like open file handles for logging). This constraint prevents unexpected behavior where detached processes remain tied to their parent's lifecycle.
The most straightforward solution is to set stdio: 'ignore' when spawning detached processes:
const { spawn } = require('child_process');
// This will fail
const child1 = spawn('my-daemon', {
detached: true
// Missing stdio configuration
});
// This works correctly
const child2 = spawn('my-daemon', {
detached: true,
stdio: 'ignore'
});
child2.unref(); // Allow parent to exitSetting stdio: 'ignore' disconnects stdin, stdout, and stderr from the parent, allowing the child to truly detach and run independently.
If you need to capture output from the detached process, redirect stdio to file descriptors:
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const logDir = './logs';
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
const outFile = fs.openSync(path.join(logDir, 'daemon.log'), 'a');
const errFile = fs.openSync(path.join(logDir, 'daemon.error.log'), 'a');
const child = spawn('my-daemon', {
detached: true,
stdio: ['ignore', outFile, errFile]
});
child.unref();This way, the child process doesn't inherit parent streams but still captures its output for debugging.
Always call unref() on the child process object after spawning with detached: true:
const { spawn } = require('child_process');
const child = spawn('my-daemon', ['--config', 'production.json'], {
detached: true,
stdio: 'ignore',
cwd: '/home/app'
});
// Critical: allow the parent process to exit without waiting for child
child.unref();
console.log('Daemon started with PID:', child.pid);
// Parent can now exit, child continues runningunref() removes the child from the parent's event loop, allowing the parent to exit without waiting for the child to finish.
For more control, pass an array specifying exact stdio handling for stdin, stdout, and stderr:
const { spawn } = require('child_process');
const fs = require('fs');
const logFile = fs.openSync('./daemon.log', 'a');
// Option 1: stdin ignored, stdout/stderr to file
const child1 = spawn('daemon', {
detached: true,
stdio: ['ignore', logFile, logFile]
});
// Option 2: all streams ignored
const child2 = spawn('daemon', {
detached: true,
stdio: 'ignore' // Shorthand for ['ignore', 'ignore', 'ignore']
});
// Option 3: stdin ignored, stdout inherited by file, stderr ignored
const errFile = fs.openSync('./error.log', 'a');
const child3 = spawn('daemon', {
detached: true,
stdio: ['ignore', logFile, errFile]
});
child1.unref();
child2.unref();
child3.unref();This gives you precise control over each stream without inheriting the parent's terminal.
If your use case doesn't actually require the child to outlive the parent, simply remove detached: true:
const { spawn } = require('child_process');
// Only use detached if the child must run independently
// If the parent waits for the child anyway, this is unnecessary
// If you don't need detached mode:
const child = spawn('command', ['arg1', 'arg2'], {
// detached: true // Not needed
stdio: 'inherit' // Parent waits, so inherit is fine
});
// If you do need true independence:
const daemon = spawn('daemon', {
detached: true,
stdio: 'ignore' // Required for detached
});
daemon.unref();Evaluate whether your use case truly requires detached mode before adding it. Some scenarios only need the parent to wait for the child to complete, in which case detached: true isn't necessary.
Terminal Attachment: A detached process uses setsid() on Unix/Linux to create a new process session, disconnecting it from the parent's controlling terminal. If stdio is inherited, the process remains attached to that terminal and won't truly detach. Windows doesn't have the same terminal attachment concept, but the constraint still exists.
Process.unref() vs Not: Calling child.unref() tells Node.js the child process shouldn't prevent the parent from exiting. Without it, the parent's event loop remains active until the child completes, defeating the purpose of detached mode.
File Descriptor Lifecycle: When using open file descriptors (from fs.openSync()), be aware these files remain open as long as the child process runs. The child inherits these open file descriptors. Consider the lifespan and closing strategy for log files in production.
Cross-Platform Behavior: detached: true has different implementations on Windows versus Unix-like systems. On Windows, spawning with detached: true uses a new process group. The stdio requirement is enforced across all platforms for consistency.
Debugging Detached Processes: Once a child is detached, it's no longer directly manageable from the parent. You'll need to track the PID (returned from spawn()) if you need to interact with the child later. Store PIDs in a file or database for management tools.
Permissions and Capabilities: Depending on the command and system configuration, the detached child may require specific permissions or capabilities. Test thoroughly in your production environment.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams