目录
- 它怎么认出崩溃、整理现场
- 实测:三段崩溃,三份现场
- 不止是命令崩溃
- 写在最后
让 AI 跑一遍测试,或者把服务启动起来,结果程序崩了。这时候最常见的动作,是把终端里那一大段报错选中、复制、贴回对话框,再问一句「这是怎么回事」。
在 WES Code 里,这一步可以省掉。AI 跑的命令一崩溃,它就会认出来,截出崩溃的那一段,解析出调用栈,存进调试记录。你下一次发消息时,这份现场会整理好附在消息里,一起交给 AI。
它怎么认出崩溃、整理现场
它看的是 AI 用 exec 跑出来的输出。退出码为 0 的、太短的输出直接跳过,这样不会在 ls、grep 这类普通输出上白跑一遍正则。剩下的按这几种特征去认:
| 语言 | 认的是什么 |
|---|---|
| Go | panic:,或者goroutine 1 [running]:这样的行 |
| Python | Traceback (most recent call last): |
| JavaScript / TypeScript | 一行TypeError:这类报错,下一行以at开头 |
| Java | Exception in thread "main"这样的行 |
| Rust | thread 'main' panicked at |
| Ruby | app.rb:12:in这样「文件名、行号、in」的行 |
认出来以后,整理成一份现场。认出是哪种语言以后,它会做三件事:
- 截出崩溃的那一段:从崩溃开始的那一行往下截,最多 50 行,前面无关的输出不要
- 解析调用栈:Go、Python、JS 的输出,逐帧解析出函数名、文件和行号,最多 20 帧
- 存进调试记录:最多保留最近 50 条,只放在内存里,不写进数据库
你下一次发消息时,它会取出最近的一份现场,整理成一个<debug-exception>块附在消息里:前面是调用栈,最多 15 帧;后面是原始输出,最多 2000 个字符。如果现场来自编辑器的调试器,还会带上崩溃点的变量值。这份现场取出来就清掉,不会每条消息都重复附一遍。
实测:三段崩溃,三份现场
我把 WES Code 里识别和整理崩溃的那几个函数原样复制出来,按它处理的顺序写了个测试:先认语言,再截现场、解析调用栈,最后整理成交给 AI 的那个块。三段输出分别模拟 Go 的数组越界、Python 的除零、Node 读取 undefined 的属性:
// 照 crashDetectHook → HandleCrashReport → GetPendingDebugOverlay 的顺序处理三段崩溃输出funcTestZZCrashDemo(t*testing.T){samples:=[]struct{name,outstring}{{"Go","Exit code: 2\npanic: runtime error: index out of range [3] with length 3\n\ngoroutine 1 [running]:\nmain.pick(...)\n\t/home/dev/shop/cart.go:42\nmain.main()\n\t/home/dev/shop/main.go:18 +0x1d\nexit status 2"},{"Python","Exit code: 1\nTraceback (most recent call last):\n File \"/home/dev/shop/app.py\", line 27, in <module>\n main()\n File \"/home/dev/shop/app.py\", line 21, in main\n total = price / qty\nZeroDivisionError: division by zero"},{"Node","Exit code: 1\nTypeError: Cannot read properties of undefined (reading 'id')\n at render (/home/dev/shop/src/cart.js:12:18)\n at main (/home/dev/shop/src/index.js:5:3)"},}for_,s:=rangesamples{lang,ok:=DetectCrash(s.out)section:=ExtractCrashSection(s.out,lang)ev:=DebugEvent{Kind:"crash",Source:"exec",CrashText:truncateCrash(section,5000),Language:lang,Frames:parseCrashFrames(section,lang)}fmt.Printf("==== %s:识别为崩溃 %v,语言 %s,解析出 %d 帧调用栈\n%s\n\n",s.name,ok,lang,len(ev.Frames),FormatCrashOverlay(ev))}// 后台服务的日志:照 ScanLogErrors 逐行扫log:="starting server on :3000\nlisten tcp :3000: bind: address already in use\nWARN cache miss\nSegmentation fault (core dumped)\nError: Cannot find module 'express'\ndial tcp: connection refused"fmt.Println("==== 服务日志")for_,m:=rangeScanLogErrors(log){fmt.Printf("[%s] %s: %s\n",m.Level,m.Pattern,m.Line)}}三段崩溃的输出:
==== Go:识别为崩溃 true,语言 go,解析出 2 帧调用栈 <debug-exception> Stack trace: /home/dev/shop/cart.go:42 — main.pick(...) /home/dev/shop/main.go:18 — main.main() Output: panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: main.pick(...) /home/dev/shop/cart.go:42 main.main() /home/dev/shop/main.go:18 +0x1d exit status 2 </debug-exception> ==== Python:识别为崩溃 true,语言 python,解析出 2 帧调用栈 <debug-exception> Stack trace: /home/dev/shop/app.py:27 — <module> /home/dev/shop/app.py:21 — main Output: Traceback (most recent call last): File "/home/dev/shop/app.py", line 27, in <module> main() File "/home/dev/shop/app.py", line 21, in main total = price / qty ZeroDivisionError: division by zero </debug-exception> ==== Node:识别为崩溃 true,语言 javascript,解析出 2 帧调用栈 <debug-exception> Stack trace: /home/dev/shop/src/cart.js:12 — render /home/dev/shop/src/index.js:5 — main Output: TypeError: Cannot read properties of undefined (reading 'id') at render (/home/dev/shop/src/cart.js:12:18) at main (/home/dev/shop/src/index.js:5:3) </debug-exception>三段都认对了语言,各解析出 2 帧调用栈。AI 拿到这份现场,第一眼就能看到 Go 那段崩在 cart.go 第 42 行的 pick 函数里,不用再去一长串输出里找。
有了文件和行号,AI 的下一步就很明确:直接打开 cart.go,读 pick 函数,看下标为什么会到 3;Python 那段就去看 app.py 第 21 行的total = price / qty,查 qty 是从哪里来的、为什么会是 0。调用栈越清楚,AI 越不用靠猜。
不止是命令崩溃
调试记录还有几个来源,最后都走同一条路交给 AI:
- 后台服务的日志:AI 在后台启动的服务,它去查看输出时,会逐行按 12 条规则扫一遍日志,比如 ERROR、FATAL、段错误、内存不足、端口被占用、模块找不到、连接被拒
- AI 在终端里启动的服务:最近 60 秒内终端里报的错,最多 5 条,会附在你的下一条消息里
- 测试失败:失败的测试名、文件、行号和报错,会整理成一个
<debug-test-failure>块 - 编辑器的调试器:调试时抛出的异常、命中的断点,也会记进调试记录
上面那段测试的最后,扫了一段模拟的服务日志,结果是这样的:
[error] address_in_use: listen tcp :3000: bind: address already in use [crash] segfault: Segmentation fault (core dumped) [error] error_keyword: Error: Cannot find module 'express' [warning] connection_refused: dial tcp: connection refused普通的WARN cache miss不会被当成异常,段错误则被标成了最严重的 crash 级别。
需要更多细节时,AI 还可以打开 debug 这组工具,自己去查最近的调试记录和调用栈。
写在最后
程序崩溃时,最有用的信息往往就在那几十行里:崩在哪个文件的哪一行,中间经过了哪些函数。这件事 WES Code 替你做了:自动把这几十行找出来、整理好,在你开口的时候一起交给 AI。你只需要说一句「看看为什么崩了」。
文中的崩溃识别是 WES Code 里的功能,官网是 weisyn.com。你平时排查崩溃,最先看的是调用栈还是日志,欢迎评论区聊聊。
觉得有用的朋友,欢迎点赞、收藏、关注,后面会继续分享 AI 编程的实战经验。