The ESRCH error occurs when attempting to send a signal to a process that no longer exists. This typically happens with process.kill() or child process operations when the target process has already exited.
The ESRCH error (Error: kill ESRCH) stands for "Error: No Such Process". This error is thrown by Node.js when you attempt to send a signal (such as SIGTERM or SIGKILL) to a process ID that does not exist or is no longer running. This commonly occurs when using process.kill() or childProcess.kill() methods. The error indicates that the operating system cannot find the process with the specified PID, usually because the process has already terminated before the signal could be delivered. The ESRCH error is particularly common in scenarios involving child processes, process managers, and automated cleanup routines where timing issues can cause a process to exit between the time you check for it and when you attempt to kill it.
Use signal 0 to test if a process exists without actually sending a kill signal:
try {
// Signal 0 checks existence without killing
process.kill(pid, 0);
console.log('Process exists');
// Now safe to kill
process.kill(pid, 'SIGTERM');
} catch (err) {
if (err.code === 'ESRCH') {
console.log('Process already exited');
} else {
throw err; // Re-throw other errors
}
}Note that there's still a small race condition window between checking and killing, but this reduces the likelihood significantly.
Always handle the ESRCH error gracefully when killing processes:
function killProcessSafely(pid, signal = 'SIGTERM') {
try {
process.kill(pid, signal);
console.log(`Successfully sent ${signal} to process ${pid}`);
return true;
} catch (err) {
if (err.code === 'ESRCH') {
// Process already exited - not an error
console.log(`Process ${pid} already exited`);
return false;
} else if (err.code === 'EPERM') {
console.error(`No permission to kill process ${pid}`);
throw err;
} else {
throw err;
}
}
}This approach treats ESRCH as expected behavior rather than an error.
When working with child processes, always listen for error events:
const { spawn } = require('child_process');
const child = spawn('node', ['script.js']);
child.on('error', (err) => {
console.error('Failed to start or kill child process:', err);
});
child.on('exit', (code, signal) => {
console.log(`Child exited with code ${code} and signal ${signal}`);
});
// Safe cleanup
function cleanup() {
if (child && !child.killed) {
try {
child.kill('SIGTERM');
} catch (err) {
if (err.code !== 'ESRCH') {
console.error('Error killing child:', err);
}
}
}
}
process.on('exit', cleanup);
process.on('SIGINT', cleanup);This ensures errors are caught and handled appropriately.
Maintain state tracking for processes you manage:
class ProcessManager {
constructor() {
this.processes = new Map();
}
spawn(id, command, args) {
const child = spawn(command, args);
this.processes.set(id, {
pid: child.pid,
process: child,
alive: true
});
child.on('exit', () => {
const proc = this.processes.get(id);
if (proc) proc.alive = false;
});
return child;
}
kill(id, signal = 'SIGTERM') {
const proc = this.processes.get(id);
if (!proc) {
console.log(`No process with id ${id}`);
return false;
}
if (!proc.alive) {
console.log(`Process ${id} already exited`);
return false;
}
try {
proc.process.kill(signal);
return true;
} catch (err) {
if (err.code === 'ESRCH') {
proc.alive = false;
return false;
}
throw err;
}
}
}This approach tracks whether processes are still alive before attempting to kill them.
Implement proper shutdown sequences with timeouts:
async function gracefulShutdown(child, timeout = 5000) {
return new Promise((resolve) => {
let killed = false;
const timer = setTimeout(() => {
if (!killed && !child.killed) {
try {
console.log('Force killing process...');
child.kill('SIGKILL');
} catch (err) {
if (err.code !== 'ESRCH') {
console.error('Error force killing:', err);
}
}
}
resolve();
}, timeout);
child.on('exit', () => {
killed = true;
clearTimeout(timer);
resolve();
});
try {
child.kill('SIGTERM');
} catch (err) {
if (err.code === 'ESRCH') {
killed = true;
clearTimeout(timer);
resolve();
} else {
throw err;
}
}
});
}
// Usage
await gracefulShutdown(child, 5000);
console.log('Process shut down successfully');This gives processes time to exit gracefully before force-killing.
Windows-specific considerations: Windows handles process signals differently than Unix-like systems. The kill() method on Windows provides emulated signal behavior, but has limitations:
- Windows does not support killing process groups with negative PIDs
- PIDs reported by Node.js may not match those shown in Task Manager
- SIGTERM and SIGKILL are treated identically on Windows (both terminate immediately)
- The node-pty and similar libraries often encounter ESRCH on Windows due to PID handling differences
Race conditions: The ESRCH error is fundamentally a race condition issue. Even with signal 0 checks, there's a tiny window where a process can exit between checking and killing. For critical applications, implement proper state tracking rather than relying solely on PID checks.
Process managers: Tools like PM2, Forever, and Nodemon handle ESRCH internally and typically don't propagate these errors to users. If you're building your own process manager, follow their pattern of treating ESRCH as informational rather than a fatal error.
Signal delivery guarantees: POSIX systems don't guarantee signal delivery if a process exits before the signal arrives. Your code should always assume that kill() might fail due to timing, and handle it gracefully.
Alternative approaches: Consider using IPC (Inter-Process Communication) channels with child processes instead of relying on signals. This allows for graceful shutdown requests that the child can acknowledge, reducing the need for forced kills.
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files