This error occurs when Node.js child_process.exec() tries to execute a command that cannot be found by the shell (/bin/sh). The shell either cannot locate the executable in PATH or the command does not exist on the system.
This error appears when using Node.js's `child_process.exec()` or related functions to execute a shell command. The `exec()` function spawns a shell (by default `/bin/sh` on Unix systems) to run your command. When you see "command not found," it means the shell could not locate the executable you're trying to run. The `/bin/sh` prefix indicates that Node.js successfully spawned the shell, but the shell itself failed to find the command. This is different from an ENOENT error on the shell path itself, which would prevent the shell from starting at all. This commonly happens when the command is not installed, not in the system PATH, or when the environment variables available to the child process differ from your normal shell environment. Docker containers and CI/CD environments are particularly prone to this issue because they often have minimal installations and different PATH configurations.
First, check if the command is actually installed. Run this in your terminal:
which <command-name>
# or
command -v <command-name>If this returns nothing, the command is not installed or not in PATH. Install it using your package manager:
# Ubuntu/Debian
sudo apt-get update && sudo apt-get install <package-name>
# Alpine (Docker)
apk add <package-name>
# macOS
brew install <package-name>When you pass a custom env object to exec(), it completely replaces the environment, including PATH. Always include PATH:
const { exec } = require('child_process');
// ❌ Wrong - this removes PATH
exec('mycommand', {
env: { MY_VAR: 'value' }
}, (error, stdout, stderr) => {
// Will fail with "command not found"
});
// ✅ Correct - preserve PATH
exec('mycommand', {
env: {
...process.env, // Include existing environment
MY_VAR: 'value'
}
}, (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error.message}`);
return;
}
console.log(stdout);
});Instead of relying on PATH, use the absolute path to the executable:
const { exec } = require('child_process');
// Find the absolute path first
const { execSync } = require('child_process');
try {
const commandPath = execSync('which node', { encoding: 'utf-8' }).trim();
console.log('Node path:', commandPath);
// Use absolute path
exec(`${commandPath} --version`, (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error.message}`);
return;
}
console.log(stdout);
});
} catch (error) {
console.error('Command not found');
}For npm/yarn global packages:
const { exec } = require('child_process');
const path = require('path');
// npm global bin directory
const npmBin = execSync('npm bin -g', { encoding: 'utf-8' }).trim();
const commandPath = path.join(npmBin, 'mycommand');
exec(commandPath, (error, stdout, stderr) => {
// ...
});If your command requires bash features (aliases, functions), explicitly use bash instead of /bin/sh:
const { exec } = require('child_process');
exec('mycommand', {
shell: '/bin/bash' // Use bash instead of sh
}, (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error.message}`);
return;
}
console.log(stdout);
});Note: This only helps if the command exists but requires bash-specific features. It won't help if the command isn't in PATH.
In Dockerfiles, ensure commands are installed and PATH is configured:
FROM node:18-alpine
# Install required commands
RUN apk add --no-cache git bash curl
# If using npm global packages, add to PATH
ENV PATH="/usr/local/bin:$PATH"
# For NVM installations
ENV NVM_DIR="/root/.nvm"
RUN source "$NVM_DIR/nvm.sh" && nvm install node
# Create symlinks if using a non-root user
RUN ln -s /usr/local/bin/node /usr/bin/node
RUN ln -s /usr/local/bin/npm /usr/bin/npm
COPY . /app
WORKDIR /app
CMD ["node", "index.js"]When using docker-compose exec:
# Pass environment variables explicitly
docker-compose exec -e PATH=$PATH app node script.js
# Or ensure PATH is set in docker-compose.yml
# services:
# app:
# environment:
# - PATH=/usr/local/bin:/usr/bin:/binUnderstanding exec() vs spawn(): The exec() function spawns a shell and runs commands through it, while spawn() runs executables directly. If you're executing a known binary without shell features, spawn() is more efficient and avoids shell lookup issues:
const { spawn } = require('child_process');
const child = spawn('/usr/bin/node', ['--version']);PATH lookup order: When a shell looks for a command, it searches directories in PATH from left to right. You can see your PATH with console.log(process.env.PATH). If multiple versions of a command exist, the first match is used.
Alpine Linux considerations: Alpine uses musl libc instead of glibc and has a minimal set of installed utilities. Many commands you expect on Ubuntu/Debian won't exist on Alpine by default. Always install required dependencies explicitly in Alpine-based containers.
NVM and PATH persistence: If you install Node.js via NVM in a Dockerfile, the PATH modifications are not automatically available to child processes. You must either source the NVM script in each shell command or use absolute paths to the NVM-installed binaries (typically in ~/.nvm/versions/node/*/bin/).
Security implications: Using absolute paths is more secure than relying on PATH, as it prevents PATH injection attacks where a malicious actor places a fake executable earlier in PATH to intercept your commands.
Error handling best practices: Always handle the 'error' event in child processes. Check error.code for ENOENT specifically, and provide helpful messages directing users to install missing dependencies.
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