← 中文资源

简体中文版本

Node.js spawn ENOENT — 子进程无法启动

定位 Node.js child_process 的 spawn ENOENT:二进制缺失、工作目录被删除,或服务进程与 shell 的 PATH 不一致。

阅读英文原文

症状

本篇调试指南针对“Node.js spawn ENOENT — 子进程无法启动”。内容涵盖可观察的症状、可复现的诊断路径、根本原因、具体修复方式,以及如何验证修复并防止回归。

可观察到的症状包括:

  • Node 在 spawn()/execFile() 中抛出 “Error: spawn ENOENT”
  • 同一条命令在终端手动执行却成功
  • 本机可用,但在 Docker 或 systemd 服务中失败

诊断

先在隔离环境中复现故障,再按顺序执行以下检查,逐步缩小范围。

步骤 1

command -v <cmd> && <cmd> --version

若 shell 也找不到,先安装;若 shell 能找到而 Node 找不到,问题在 PATH 或工作目录,而非安装。

步骤 2

node -e "console.log(process.env.PATH); console.log(process.cwd())"

与 shell 的 PATH 对比;服务常以精简 PATH 启动,缺少 nvm/asdf 等垫片目录。

步骤 3

node -e "require('child_process').spawnSync('/usr/bin/ffmpeg',['-version'],{stdio:'inherit'})"

绝对路径可用即证明程序存在,只是 PATH 解析失败。

常见诱因(按出现频率排序):

  • 可执行文件未安装,或不在 Node 进程可见的 PATH 上
  • cwd 指向已不存在的目录,导致相对路径无法解析
  • 服务单元以精简 PATH 启动,缺少 /usr/local/bin 或版本管理垫片目录

根因

spawn() 在未启用 shell 时直接调用 execvp,只搜索进程级 PATH,不会经过登录 shell。若 PATH 缺少该程序,或 cwd 已消失导致相对路径无法解析,内核返回 ENOENT,Node 便抛出 spawn ENOENT。

修复

启动时用 which/where 将可执行文件解析为绝对路径,显式传入包含预期 PATH 的 env,把 cwd 设为确实存在的目录;仅当需要 shell 内建命令时才使用 shell:true。

注意:shell:true 拼接用户输入会引发命令注入,切勿把参数拼进命令字符串。

import { spawn, execFileSync } from 'node:child_process';

// Resolve once at boot, then reuse.
const bin = execFileSync('which', ['ffmpeg']).toString().trim();
const child = spawn(bin, ['-version'], {
  stdio: 'inherit',
  cwd: process.cwd(),
  env: { ...process.env, PATH: '/usr/local/bin:/usr/bin:/bin' },
});
child.on('error', (err) => console.error('spawn failed:', err.code, bin));

验证

再次运行子进程并确认退出码为 0。启动时记录解析到的绝对路径并断言文件存在,让缺失程序快速失败并给出清晰信息,而非晦涩的 ENOENT。

修复后补充一条回归测试,复现原始症状,防止问题再次出现。

这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。