Skip to content

fix(c,go): 修复 cagent SIGSEGV 崩溃 + 崩溃后终端 raw mode 恢复#70

Merged
lloydzhou merged 2 commits into
mainfrom
fix/crash-terminal-restore
Jul 14, 2026
Merged

fix(c,go): 修复 cagent SIGSEGV 崩溃 + 崩溃后终端 raw mode 恢复#70
lloydzhou merged 2 commits into
mainfrom
fix/crash-terminal-restore

Conversation

@lloydzhou

Copy link
Copy Markdown
Owner

问题

cagent 在 SubAgent 嵌套调用场景下发生 SIGSEGV 崩溃(4 份 macOS crash log),
崩溃后终端残留在 raw mode,输出完全错乱,需手动 stty sane 恢复。

Bug 1: SubAgent NULL display_queue → SIGSEGV (addr=0x18) — C 版本

触发条件: SubAgent(display_queue=NULL)内部 LLM 调用 SubAgent 工具(嵌套),
主 agent 为 human 输出模式。

根因: agent_handle_sub_agent / agent_process_sub_result 直接调 mq_push
绕过了已有 NULL 检查的 push_display 辅助函数。SubAgent display_queue=NULL →
mq_push 解引用 NULL 偏移 0x18(pthread_mutex_t.mutex)→ 段错误。

Bug 2: json_parse_root(NULL) → SIGSEGV (addr=0x0) — C 版本

根因: tool call 的 input_json.data 为 NULL 时,json_parse_root 未做 NULL 检查,
skip_ws(NULL, &pos) 解引用空指针。

终端排版错乱 — C + Go 版本

linenoise readline 线程将终端设为 raw mode(禁用 OPOST/ONLCR)。进程崩溃后
atexit 不触发,raw mode 残留。

修复

Commit 1: C 版本(2 bugs + 终端恢复)

文件 改动
c/agent.c agent_handle_sub_agent / agent_process_sub_resultmq_push 前增加 display_queue != NULL 条件
c/json.c json_parse_root 入口增加 if (!src) return make_err
c/msgqueue.c mq_push / mq_pop / mq_try_pop 防御性 NULL 检查
c/cagent.c SIGSEGV/SIGABRT/SIGBUS/SIGFPE signal handler,恢复终端后重新发出信号
vendor/linenoise/linenoise.{c,h} 公开 linenoiseRestoreTerminal() 函数

Commit 2: Go 版本(终端恢复加固)

Go 版本不存在 Bug 1 和 Bug 2(语言层面天然保护:nil channel send 会 block 而非 crash;
encoding/json 对空输入返回 error 而非 panic)。但 Go 同样使用 CGo linenoise 进入 raw mode,
缺少崩溃恢复机制。

文件 改动
go/cmd/goagent/main.go defer recover() + signal.Notify(SIGSEGV/SIGABRT/SIGBUS/SIGFPE) 恢复终端
go/cmd/goagent/readline.go readline goroutine defer recover() 恢复终端
go/linenoise/linenoise.go RestoreTerminal() Go 绑定,调用 C 版 linenoiseRestoreTerminal()

Rust 版本

已有 std::panic::set_hookcrossterm::terminal::disable_raw_mode(),无需修改。

测试

  • C: make build-c + AGENT=./dist/cagent bash tests/test.sh201 passed, 0 failed
  • Go: make build-go + AGENT=./dist/goagent bash tests/test.sh201 passed, 0 failed

…GSEGV 崩溃

Bug 1 (SIGSEGV at 0x18): SubAgent mq_push(NULL display_queue)
- agent_handle_sub_agent/process_sub_result 直接调 mq_push 绕过 NULL 检查
- SubAgent display_queue=NULL, 交互模式下 mq_push 解引用偏移 0x18 段错误
- 修复: mq_push 前增加 display_queue != NULL 条件

Bug 2 (SIGSEGV at 0x0): json_parse_root(NULL) 解引用空指针
- tool call input_json.data 为 NULL 时 skip_ws 段错误
- 修复: json_parse_root 入口增加 NULL 检查

防御性修复:
- mq_push/mq_pop/mq_try_pop 增加队列 NULL 检查
- crash signal handler 恢复终端 raw mode 防崩溃后终端错乱
Go 版本使用 CGo linenoise 进入终端 raw mode,但崩溃/panic 时没有恢复机制,
导致终端残留在 raw mode(无回显、无换行处理),需要手动 stty sane 修复。

与 C 版本的修复保持一致:
- main(): defer recover() 捕获主 goroutine panic,恢复终端后退出
- main(): signal.Notify 捕获 SIGSEGV/SIGABRT/SIGBUS/SIGFPE,恢复终端后重新发出信号
- readline loop(): defer recover() 捕获 readline goroutine panic
- linenoise.RestoreTerminal(): 公开 Go 绑定,调用 C 版 linenoiseRestoreTerminal()

注:Go 版本不存在 C 版本的两个 SIGSEGV bug(Bug 1: SubAgent 用 silent TermDisplay
而非 nil channel;Bug 2: encoding/json 对空输入返回 error 而非 panic)。
本次修复纯粹是终端恢复加固。
@lloydzhou lloydzhou merged commit 11a5007 into main Jul 14, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant