如何为 Node.js 多层子进程启动调试(以 OpenClaw 为例)

如何为 Node.js 多层子进程启动调试(以 OpenClaw 为例)

问题背景

项目使用 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 沿着进程链一路传递到最终的业务进程,同时避免多个进程抢占同一调试端口。

具体改动

1. scripts/watch-node.mjs --- 分离 inspect flag,不污染其他子进程

原来的逻辑会把所有参数(包括 --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 的构建

2. scripts/run-node.mjs --- 将 inspect flag 转发给业务进程

复制代码
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 端口冲突。

3. tsdown.config.ts --- debug 构建时开启 sourcemap

复制代码
const isDebugBuild = process.env.OPENCLAW_DEBUG_BUILD === "1";

// 在 nodeBuildConfig 中:
...(isDebugBuild ? { sourcemap: true, minify: false } : {}),

没有 sourcemap,DevTools 只能看到编译后的 JS,无法映射到 TypeScript 源文件。

4. package.json --- 新增调试命令

复制代码
"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 源文件,可正常打断点调试。

关键经验

  1. 多层子进程调试,每层都需要 --inspect,但不能共享同一端口
  2. --inspect=0 而不是固定端口,让 OS 自动分配,彻底避免冲突
  3. 不要用 NODE_OPTIONS 传 inspect flag,会污染所有子孙进程,导致批量端口冲突
  4. sourcemap 缺失是常见遗漏,生产构建通常关闭 sourcemap,调试时必须显式开启
相关推荐
CharlesYu0113 小时前
前端性能优化的第一性原理,是不断缩短“用户发起意图 → 获得可用结果”之间的时间
前端
平头哥技术团队13 小时前
Day 21 _ 页内锚点_给每段起个 id,目录写 href=_#id_,点一下页面就滚到那一段
前端·html·html5
子兮曰15 小时前
Bun v1.4.1 深度解析:从 Zig 到 Rust,一场 11 天、64 个 AI 代理的语言迁徙
前端·后端·bun
人民广场吃泡面15 小时前
什么是AI Agent?它又能给前端带来哪些效率提升?
前端·人工智能
中科三方15 小时前
两家域名注册商资质被ICANN终止:企业域名资产安全再受关注
前端·网络·安全·域名
bug总结15 小时前
uniapp vue3全局方法注册使用
前端·javascript·uni-app
华无丽言16 小时前
如何在宜搭中实现获取子表中的字段值赋值到父表中?
前端·javascript·低代码
IT_陈寒16 小时前
Vue的computed属性竟然坑了我一把
前端·人工智能·后端
威斯软科的老司机17 小时前
通俗讲解 CNN 图像识别、向量 Embedding、Softmax 概率计算这三块的简化原理
前端·人工智能·ui·数字孪生
泯泷17 小时前
手搓JSVM第 12 篇:完整最小 JSVM 实现与源码设计复盘
前端·javascript·前端框架