文章目录
- 原来的逻辑会把所有参数(包括 –inspect)直接传给子进程,导致 NODE_OPTIONS 污染所有下游进程引发端口冲突。改为显式分离: const inspectFlags = deps.args.filter( (a) => a === “–inspect” || a === “–inspect-brk” || a.startsWith(“–inspect=”) || a.startsWith(“–inspect-brk=”), ); const filteredArgs = deps.args.filter((a) => !inspectFlags.includes(a)); if (inspectFlags.length > 0) { childEnv.OPENCLAW_DEBUG_BUILD = “1”; childEnv.OPENCLAW_INSPECT_FLAGS = inspectFlags.join(” “); } const watchProcess = deps.spawn(execPath, […inspectFlags, …buildWatchArgs(filteredArgs)], …); inspectFlags 作为 Node 参数直接传给中间进程(不经过 shell 环境变量) OPENCLAW_INSPECT_FLAGS 存入环境变量,供下一层读取 OPENCLAW_DEBUG_BUILD=1 触发带 sourcemap 的构建
- const inspectNodeArgs = deps.env.OPENCLAW_INSPECT_FLAGS ? deps.env.OPENCLAW_INSPECT_FLAGS.split(” “) .filter(Boolean) .map((flag) => { if (flag === “–inspect”) return “–inspect=0”; if (flag === “–inspect-brk”) return “–inspect-brk=0”; return flag; }) : []; const nodeProcess = deps.spawn(execPath, […inspectNodeArgs, “openclaw.mjs”, …deps.args], …); 使用 –inspect=0 让操作系统自动分配空闲端口,避免与父进程的 9229 端口冲突。
- const isDebugBuild = process.env.OPENCLAW_DEBUG_BUILD === “1”; // 在 nodeBuildConfig 中: …(isDebugBuild ? { sourcemap: true, minify: false } : {}), 没有 sourcemap,DevTools 只能看到编译后的 JS,无法映射到 TypeScript 源文件。
- “gateway:watch:debug”: “node scripts/watch-node.mjs –inspect gateway –force”
目录
- 问题背景
- 根本原因
- 解决思路
- 具体改动
- 1.scripts/watch-node.mjs— 分离 inspect flag,不污染其他子进程
- 2.scripts/run-node.mjs— 将 inspect flag 转发给业务进程
- 3.tsdown.config.ts— debug 构建时开启 sourcemap
- 4.package.json— 新增调试命令
- 最终效果
- 关键经验
项目使用 pnpm gateway:watch:debug 启动后,在 chrome://inspect/#devices 的 Sources 页签只能看到 scripts/run-node.mjs,看不到任何业务代码文件。
该项目的启动链分三层:
watch-node.mjs(顶层 watch 进程)
└── node --inspect --watch ... run-node.mjs(中间层,负责构建判断)
└── node openclaw.mjs(实际业务进程)
--inspect flag 只加在了中间层 run-node.mjs 上,真正运行业务代码的 openclaw.mjs 没有携带该 flag,所以 Chrome DevTools 看不到业务代码。
需要让 --inspect 沿着进程链一路传递到最终的业务进程,同时避免多个进程抢占同一调试端口。
原来的逻辑会把所有参数(包括 --inspect)直接传给子进程,导致 NODE_OPTIONS 污染所有下游进程引发端口冲突。改为显式分离:
const inspectFlags = deps.args.filter(
(a) => a === "--inspect" || a === "--inspect-brk"
|| a.startsWith("--inspect=") || a.startsWith("--inspect-brk="),
);
const filteredArgs = deps.args.filter((a) => !inspectFlags.includes(a));
if (inspectFlags.length > 0) {
childEnv.OPENCLAW_DEBUG_BUILD = "1";
childEnv.OPENCLAW_INSPECT_FLAGS = inspectFlags.join(" ");
}
const watchProcess = deps.spawn(execPath, [...inspectFlags, ...buildWatchArgs(filteredArgs)], ...);
inspectFlags作为 Node 参数直接传给中间进程(不经过 shell 环境变量)OPENCLAW_INSPECT_FLAGS存入环境变量,供下一层读取OPENCLAW_DEBUG_BUILD=1触发带 sourcemap 的构建
const inspectNodeArgs = deps.env.OPENCLAW_INSPECT_FLAGS
? deps.env.OPENCLAW_INSPECT_FLAGS.split(" ")
.filter(Boolean)
.map((flag) => {
if (flag === "--inspect") return "--inspect=0";
if (flag === "--inspect-brk") return "--inspect-brk=0";
return flag;
})
: [];
const nodeProcess = deps.spawn(execPath, [...inspectNodeArgs, "openclaw.mjs", ...deps.args], ...);
使用 --inspect=0 让操作系统自动分配空闲端口,避免与父进程的 9229 端口冲突。
const isDebugBuild = process.env.OPENCLAW_DEBUG_BUILD === "1";
// 在 nodeBuildConfig 中:
...(isDebugBuild ? { sourcemap: true, minify: false } : {}),
没有 sourcemap,DevTools 只能看到编译后的 JS,无法映射到 TypeScript 源文件。
"gateway:watch:debug": "node scripts/watch-node.mjs --inspect gateway --force"
启动后终端输出:
Debugger listening on ws://127.0.0.1:XXXXX/...
在 chrome://inspect/#devices 中可以看到业务进程的调试 target,点击后 Sources 页签显示完整的 TypeScript 源文件,可正常打断点调试。
- 多层子进程调试,每层都需要
--inspect,但不能共享同一端口
- 用
--inspect=0 而不是固定端口,让 OS 自动分配,彻底避免冲突
- 不要用
NODE_OPTIONS 传 inspect flag,会污染所有子孙进程,导致批量端口冲突
- sourcemap 缺失是常见遗漏,生产构建通常关闭 sourcemap,调试时必须显式开启
--inspect,但不能共享同一端口--inspect=0 而不是固定端口,让 OS 自动分配,彻底避免冲突NODE_OPTIONS 传 inspect flag,会污染所有子孙进程,导致批量端口冲突到此这篇关于Node.js 多层子进程启动调试的问题解决(以 OpenClaw 为例)的文章就介绍到这了,更多相关OpenClaw Node.js 多层子进程内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!