http://192.168.31.76:8680)+ grok CLI 1.0.41报错 出错了: prompt: {"code":-32602,"message":"Invalid params","data":"unknown session id"} 是两个条件叠加的结果,不是 grok 坏了,也不是网络问题:
session.idle_timeout = "30m"。你离开超过 30 分钟,ZACP 就把 grok agent 进程杀掉回收内存(这是它的设计),而 grok 的会话(session id)只存在于 进程内存里 → 磁盘上 ZACP 的会话记录还在,但 agent 已经不认这个 id 了。session/load 恢复上下文 → 重试这条 prompt」的自愈逻辑;但它的失效判定要求错误文本里 同时出现 "session" 和一段 session id,而 grok 的报错恰恰不带 id (只有 unknown session id 五个词)→ 判定失败 → 自愈逻辑从未被触发 → 直接把错误抛给前端。修复 = 给 ZACP 打一个 6 行补丁,让判定认识 grok 的措辞。修好后实测: 杀掉 agent 后继续发消息,回复照常返回,而且上一轮对话内容(暗号「紫电青霜」)完整保留 ——因为 grok 支持 ACP session/load,能从磁盘把整个会话回放进新进程。
| 项 | 值 |
|---|---|
| 报错原文 | 出错了: prompt: {"code":-32602,"message":"Invalid params","data":"unknown session id"} |
| 触发时机 | 离开较长时间(≥30 分钟)后回来发消息 |
| ZACP | v0.9.0,commit 9360fc6,pm2 托管,监听 :8680(~/.local/bin/zacp → ~/.zacp/bin/zacp-0.9.0) |
| 配置 | ~/.zacp/config.toml,[[agents]] id="grok",args=["agent","--always-approve","stdio"] |
| grok | grok 1.0.41 (4220f3b224a6),~/.grok/bin/grok |
| 数据 | ~/.zacp/data/zacp.db(sqlite),会话消息 + acp_session_id |
| 日志 | ~/.pm2/logs/zacp-out.log / zacp-error.log |
历史报错(修复前 zacp-error.log 里的三条,全部形态一致):
time=2026-09-24T17:56:27 level=ERROR msg="handle prompt error" error="prompt: {\"code\":-32602,\"message\":\"Invalid params\",\"data\":\"unknown session id\"}"
time=2026-09-24T20:10:40 level=ERROR msg="handle prompt error" error="prompt: {...\"data\":\"unknown session id\"}"
time=2026-09-24T20:10:51 level=ERROR msg="handle prompt error" error="prompt: {...\"data\":\"unknown session id\"}"
idle_timeout 回收 agent,会话随进程消失config.toml 里 [session] idle_timeout = "30m"。ZACP 源码里这个回收器的注释写得很直白 (backend/internal/acp/manager/manager.go → recycleIdle):
回收 = 杀掉 agent 进程并移除连接;DB 中该 agent 的 session 会在下次使用时经 unknown-session 自动恢复逻辑(RecoverSession)重建。
时间线与报错逐秒对得上(两次独立复现):
| 会话 | DB 最后一次活动 | +30 分钟 = 预期回收 | 实际报错时刻 |
|---|---|---|---|
| #1 爽文小说《征召》 | 17:11:45 | ≈17:41 | 17:56:27 |
| #3 敦煌飞天出图 | 19:00:07 | ≈19:30 | 20:10:40 / 20:10:51 |
进程侧也吻合:报错发生时刻 grok agent 才刚被拉起(ps 显示 agent 启动于 20:10, 父进程是 zacp),说明发消息时 ZACP 起了一个全新 agent 进程,而它内存里当然没有旧 session。
ZACP 的判定函数(manager.IsUnknownSessionErr)核心逻辑:
msg := strings.ToLower(err.Error())
if !strings.Contains(msg, "session") || !uuidLikeRe.MatchString(msg) { // uuidLikeRe = [0-9a-f]{8,}
return false
}
即:必须同时含 "session" 和一段 ≥8 位的 uuid/hex 前缀,才认为「会话失效」。 把 grok 的报错代进去:
prompt: {"code":-32602,"message":"invalid params","data":"unknown session id"}
↑ 唯一像 id 的片段是 "32602",只有 5 位
实测(正则等价复算):hex runs: ['c','de','32602','e','a','e','a','d','a','a','da','a','e','d'], 最长 5 位 → [0-9a-f]{8,} 不匹配 → IsUnknownSessionErr 返回 false → recoverSession / RecoverSession 这条自愈分支永远进不去 → 错误原样抛给前端。
对比:ZACP 已适配的其他 agent 报错都带 id,所以从来没暴露过这个洞:
Unsupported ACP session: <uuid>session/<uuid>: unknown session <uuid>Session not found: <uuid>unknown session id(无 id) ← 漏的就是它判定里「要求 id 共现」本意是防误伤(例如 unsupported session config option: foo 这种 参数类错误不能当会话失效处理,否则会静默重建会话、丢上下文)。grok 的固定短语 unknown session id 语义唯一、无歧义,单独放行是安全的。
command = "grok" 依赖 PATH(隐患)ZACP 由 pm2 托管,进程环境是 pm2 缓存的,PATH 里没有 ~/.grok/bin。 本次排查中用 pm2 restart --update-env 刷新环境后立刻暴露:
level=WARN msg="failed to preload agent" agent=grok err="start agent 'grok': start grok: exec: \"grok\": executable file not found in $PATH"
即:只要 pm2 的环境变一次(重启机器、重装、换 shell 启动),整个 grok WebUI 就会起不来。 已顺手改成绝对路径并显式钉住代理环境变量(见 §3.4)。
msg := strings.ToLower(err.Error())
+ // grok(x.ai):`{"code":-32602,"message":"Invalid params","data":"unknown session id"}`。
+ // 该措辞不含 session id 本身,uuid 共现条件永远无法满足,故单独放行;
+ // 短语唯一指向「会话 id 未知」,不存在误伤(见下方前置条件说明)。
+ if strings.Contains(msg, "unknown session id") {
+ return true
+ }
if !strings.Contains(msg, "session") || !uuidLikeRe.MatchString(msg) {
return false
}
同时在函数文档注释里补上 grok 这一条措辞。补丁文件: grok-unknown-session-fix.patch(同目录,git apply 可直接打)。 另附回归测试 session_err_test.go(注意:上游 .gitignore 忽略 *_test.go, 要入库需 git add -f)。
上游 go.mod 要求 go 1.25.7,系统 apt 只有 1.22,且前端构建强制用 bun (scripts/build.sh 会直接报错退出)。本次已装好并留在本机:
| 组件 | 位置 | 来源(本网可达的镜像) |
|---|---|---|
| Go 1.25.7 | ~/.local/go1.25.7 | https://mirrors.aliyun.com/golang/go1.25.7.linux-amd64.tar.gz(22 MB/s) |
| bun 1.4.2 | ~/.bun/bin/bun | https://registry.npmmirror.com/-/binary/bun/bun-v1.4.2/bun-linux-x64.zip |
| 前端依赖 | frontend/node_modules | bun install --registry https://registry.npmmirror.com(709 包 / 5.4s) |
| Go 模块 | — | GOPROXY=https://goproxy.cn,direct |
⚠️ 坑:go.dev 直连只有 ~30 KB/s(78MB 要下很久),GitHub Release 资产直连拉不动 (github.com/.../releases/latest/download/... 返回 000),但 git clone 是通的。
一键重建脚本见同目录 rebuild-zacp-grokfix.sh。
~/.zacp/bin/zacp-0.9.0 官方原版 12MB(未动,回滚目标)
~/.zacp/bin/zacp-0.9.0.orig-20260924-202814 官方原版备份
~/.zacp/bin/zacp-0.9.0-grokfix 本次补丁版 43MB(buildTime 2026-09-24T12:31:11Z)
~/.local/bin/zacp -> ~/.zacp/bin/zacp-0.9.0-grokfix
mv -f 原子替换(运行中的进程持旧 inode,不会被 ETXTBSY 卡住), 然后 pm2 restart zacp。GET /api/v1/version 返回 buildTime: 2026-09-24T12:31:11Z(官方版是 08-23), 确认跑的是新二进制。file 显示 no section header), 本机这份是未压缩的 43MB,功能一致,不影响使用。~/.zacp/config.toml)[session]
idle_timeout = "8h" # 原 "30m"
[[agents]]
id = "grok"
name = "Grok"
enabled = true
command = "/home/zyw/.grok/bin/grok" # 原裸命令 "grok"(PATH 依赖,见 §2.3)
args = ["agent", "--always-approve", "stdio"]
env = [
"HTTPS_PROXY=http://127.0.0.1:10809",
"HTTP_PROXY=http://127.0.0.1:10809",
"NO_PROXY=127.0.0.1,localhost,192.168.31.0/24", # grok 调 GPU 机 192.168.31.31 不走代理
]
8h 的用意:同一天内回来不用重启 agent(恢复更快、也不吃重新回放的 token); 跨夜回收也没关系——补丁已经让回收变成无感自愈。想彻底不回收就写 "0"。env 显式钉住代理:x.ai 必须走本机 v2ray(127.0.0.1:10809,已验证 curl -x ... https://api.x.ai/v1/models 返回 401=可达)。这样不再依赖 pm2 缓存环境。~/.zacp/config.toml.bak-idlefix-20260924-202814。pm2 save 之前没有包含 zacp(pm2-zyw.service 是 enabled+active,但 dump 里只有 deepseek-harness)→ 重启机器后 ZACP 不会自动回来。已执行 pm2 save, 现在 dump 内进程为 ['deepseek-harness', 'zacp']。
| # | 验证项 | 方法 | 结果 |
|---|---|---|---|
| 1 | 判定函数 | go test ./internal/acp/manager/ -run TestIsUnknownSessionErr | 8 个子用例全 PASS(含 grok 正例 + 3 个反例不误判) |
| 2 | grok 是否支持 session/load | 直接对 grok 跑 ACP 握手探测 | 支持:session/load 返回正常,并把历史以 isReplay:true 全量回放 |
| 3 | REST 路径自愈 | 建测试会话 → 提问记暗号 → kill 掉 grok agent → 再问暗号 | 第 1 轮 已记住(14.2s);杀 agent(pid 2829294) 后第 2 轮回 紫电青霜(8.5s),新 agent pid 2830108 → 上下文经 session/load 完整保留 |
| 4 | WS 路径(你实际用的那条) | 再杀掉 agent → 直连 ws://127.0.0.1:8680/api/v1/ws 发 prompt | turn.done reply="紫电青霜",49 个流事件,全程无 error 消息 |
小观察:自愈时前端会先后收到两次
turn.started(第一次尝试被 agent 拒绝 → 恢复后重试), 属于观感噪音,不影响结果。
直接证据(修复后 zacp-error.log 里第一次出现这条 WARN,修复前三个月的日志里从未出现):
time=2026-09-24T20:30:19 level=WARN msg="acp session invalid, recovering" sessionID=01a0d364-37e7-7613-adce-ba6b6a9f5a1f err="prompt: {\"code\":-32602,\"message\":\"Invalid params\",\"data\":\"unknown session id\"}"
同一个错误文本,改前是 level=ERROR msg="handle prompt error"(抛给前端), 改后是 level=WARN msg="acp session invalid, recovering"(识别成功、走向自愈)。
测试残留已清理:测试会话已删(DELETE /api/v1/sessions/4、/5), 测试时新建的工作区 2 已软删除;你的 3 个原会话(#1 #2 #3)与工作区 /home/zyw/Pictures/Wallpapers 原样保留。
ln -sfn /home/zyw/.zacp/bin/zacp-0.9.0 /home/zyw/.local/bin/zacp
cp ~/.zacp/config.toml.bak-idlefix-20260924-202814 ~/.zacp/config.toml
pm2 restart zacp
install.sh)官方 install.sh 会把 ~/.local/bin/zacp 重新指向官方新版——补丁会丢, 「离开久了再发消息」的报错会回来(本补丁尚未提交上游)。届时二选一:
bash rebuild-zacp-grokfix.sh(会把 VER 改成新版号),或upstream-fix/ 里的补丁提给 helloxz/zacp(这个洞对任何「不适配 grok 措辞」的 判定都成立,属于通用修复)。| 文件 | 说明 |
|---|---|
grok-unknown-session-fix.patch | 对 manager.go 的最小补丁(git apply 可用) |
session_err_test.go | 回归测试(放进 backend/internal/acp/manager/,go test 可跑) |
rebuild-zacp-grokfix.sh | 一键:拉源码 → 打补丁 → 装工具链 → 构建 → 原子替换 → 重启 pm2 |
2026-09-24-ZACP-聊天图片不显示-根因与修复.md | 后续问题:修好会话恢复后发现的「聊天里的图片不显示(断裂图)」,含第二个前端补丁 chat-image-workspace-url.patch |
相关源码位置(本机临时目录,重启会清):/tmp/zacp-src(v0.9.0 + 已打补丁)。
ZACP 里
unknown session id这类「agent 侧会话失效」错误,本应被IsUnknownSessionErr捕获并自动session/load恢复;它对错误文本的要求是 「含 session」+「含 ≥8 位 id」。任何不带 session id 的措辞都会漏判, 表现就是离开一段时间后发消息直接报错。新接 agent 若报错里没有 id, 就该在manager.go的白名单/放行分支里补一条。