这次想解决的问题很具体:
在 VS Code 里调试 Mastra 这类 Node.js / TypeScript 框架时,为什么断点已经打上了,却一直显示
Unbound breakpoint?
最后发现,问题不在 VS Code 是否 attach 成功,也不在 Node Inspector 是否启动,而在源码和运行时代码之间缺少 sourcemap 映射。
先区分两个问题
Node.js 程序要能被 VS Code 调试,至少要过两关:
- Node 进程要以 Inspector 模式启动。
- VS Code 要能把源码位置映射到当前运行的 JavaScript 代码。
第一关解决的是“调试器能不能连上进程”。第二关解决的是“源码里的断点能不能落到运行时代码上”。
这两个问题很容易混在一起。看到 Unbound breakpoint 时,直觉上会怀疑 VS Code 没连上 Node 进程。但这次的现象说明,连接本身可能是正常的,真正断掉的是 sourcemap。
Mastra 的普通启动和 Inspector 启动
Mastra 普通开发启动一般是:
npm run mastra dev如果要开启 Node Inspector,可以让 Mastra dev server 以 inspect mode 启动。比如直接使用 Mastra CLI:
npx mastra dev --inspect=9229Inspector 默认端口通常是 9229。启动后,VS Code 可以 attach 到这个 Node 进程,Chrome 浏览器也可以通过 DevTools 连接。
如果项目里是通过 npm script 包了一层 Mastra CLI,需要注意把参数传给底层命令。可以根据项目脚本选择类似下面的形式:
npm run mastra -- dev --inspect=9229关键不是命令表面长什么样,而是最终运行的 Mastra dev server 必须带上 --inspect。
VS Code attach 配置
VS Code 侧需要在 .vscode/launch.json 里增加一个 attach 配置:
{
"version": "0.2.0",
"configurations": [
{
"name": "Attach Mastra",
"type": "node",
"request": "attach",
"port": 9229,
"restart": true,
"sourceMaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
],
"skipFiles": [
"<node_internals>/**"
]
}
]
}这里有两个配置值得特别注意:
sourceMaps: true:告诉 VS Code 使用 sourcemap。resolveSourceMapLocations:限制 sourcemap 解析范围,避免进入node_modules造成混乱。
配置好以后,如果 Node 进程确实以 Inspector 模式启动,VS Code 就可以 attach 到端口 9229。
为什么还会出现 Unbound breakpoint
这次真正卡住的地方是:VS Code 已经 attach 上了,但源码断点仍然显示 Unbound breakpoint。
Unbound breakpoint 的意思是:
VS Code 知道你想在这个源码位置打断点,但当前正在运行的 Node.js 进程里,没有找到它能对应上的那份代码。
这在 Node.js / TypeScript 项目里很常见,因为源码通常不是被 Node.js 直接执行的。中间可能经过很多层:
- TypeScript 编译
- ESM 转换
tsx/ts-node- Vite / tsup / esbuild
- Mastra dev server
- 动态加载
- 热更新
只要中间任何一层 sourcemap 没有接上,断点就可能飘走。
所以看到 Unbound breakpoint 时,不要只问“VS Code 为什么没连上”,还要问:
当前运行的 JavaScript 代码,能不能反向映射回我正在看的 TypeScript 源码?
关键判断:运行时代码能断,源码不能断
这次排查里最关键的观察是:
Mastra 会生成 .mastra/output 目录,里面有实际运行的 .mjs 文件。直接在 .mastra/output 里的 .mjs 文件上打断点,是可以命中的。
这说明:
- Node Inspector 已经启动。
- VS Code attach 是有效的。
- 当前 Node 进程确实可以被调试。
- 问题不在“调试器没连上”,而在“源码和编译后代码没有映射上”。
于是问题就收窄了:
TypeScript 源码断点不能命中
-> .mastra/output/*.mjs 断点可以命中
-> 调试链路本身正常
-> 源码到运行时代码的 sourcemap 映射缺失查看 .mastra/output 后发现,当前没有 sourcemap。也就是说,VS Code 只能看到运行中的 .mjs,但不知道它对应到源码里的哪一行。
两种临时解决方式
第一种方式,是直接在源码里写 debugger:
debugger只要代码实际执行到这里,Node Inspector 就会停住。这种方式不依赖你在 VS Code 里手动绑定源码断点,适合快速确认某段代码有没有被执行。
但它不是最理想的长期方案。因为它解决的是“强行停住”,不是“让源码断点正常工作”。
第二种方式,是开启 Mastra 的 bundler sourcemap。
根据 Mastra 官方配置,可以在 Mastra 初始化时打开:
import { Mastra } from '@mastra/core'
export const mastra = new Mastra({
bundler: {
sourcemap: true,
},
})bundler.sourcemap 的作用是让 Mastra 在打包运行时代码时生成 sourcemap。这样 VS Code attach 到 Node 进程后,才能把 .mastra/output 里的运行时代码映射回原始源码。
这次经验真正沉淀的判断
以后在 Node.js / TypeScript 项目里遇到源码断点不生效,可以按这个顺序排查:
- 先确认 Node 进程是否以 Inspector 模式启动。
- 再确认 VS Code 是否成功 attach 到对应端口。
- 然后去运行时代码位置打断点,看能不能命中。
- 如果运行时代码能断、源码不能断,优先怀疑 sourcemap。
- 检查框架或 bundler 是否真的生成了 sourcemap。
- 再检查 VS Code 的
sourceMaps和resolveSourceMapLocations配置。
这比一开始就反复调整 VS Code 配置更稳。
真正的判断点是:
如果编译后的运行时代码可以断,但源码断点一直 unbound,那么调试器链路大概率是通的,问题更可能在 sourcemap 映射。
用一句话总结
Mastra 这类 Node.js / TypeScript 框架里,VS Code attach 成功不等于源码断点就一定能命中。源码断点能不能命中,取决于运行时代码和源码之间是否有可用的 sourcemap。
这次的解决路径是:
npx mastra dev --inspect=9229
-> VS Code attach 9229
-> 发现源码断点 Unbound breakpoint
-> 验证 .mastra/output/*.mjs 可以断
-> 判断 Inspector 正常,sourcemap 缺失
-> 开启 Mastra bundler.sourcemap
-> 源码断点可以映射到运行时代码调试 Node.js 程序时,不要只看“有没有连上调试器”。更重要的是确认三件事是否闭合:
源码
-> sourcemap
-> 当前 Node 进程正在运行的 JavaScript这三者闭合以后,VS Code 里的源码断点才真正有地方落下去。