2026-09-24-ZACP-Grok-WebUI-unknown-session-id诊断与修复.md

ZACP + Grok WebUI「unknown session id」报错:根因与修复


0. 结论速览(TL;DR)

报错 出错了: prompt: {"code":-32602,"message":"Invalid params","data":"unknown session id"} 是两个条件叠加的结果,不是 grok 坏了,也不是网络问题:

  1. 触发条件:ZACP 的 session.idle_timeout = "30m"。你离开超过 30 分钟,ZACP 就把 grok agent 进程杀掉回收内存(这是它的设计),而 grok 的会话(session id)只存在于 进程内存里 → 磁盘上 ZACP 的会话记录还在,但 agent 已经不认这个 id 了。
  2. 放大条件(真正的 bug):ZACP 本来就有「检测到 agent 侧会话失效 → 自动 session/load 恢复上下文 → 重试这条 prompt」的自愈逻辑;但它的失效判定要求错误文本里 同时出现 "session" 和一段 session id,而 grok 的报错恰恰不带 id (只有 unknown session id 五个词)→ 判定失败 → 自愈逻辑从未被触发 → 直接把错误抛给前端。

修复 = 给 ZACP 打一个 6 行补丁,让判定认识 grok 的措辞。修好后实测: 杀掉 agent 后继续发消息,回复照常返回,而且上一轮对话内容(暗号「紫电青霜」)完整保留 ——因为 grok 支持 ACP session/load,能从磁盘把整个会话回放进新进程。


1. 现象与环境

项值
报错原文出错了: prompt: {"code":-32602,"message":"Invalid params","data":"unknown session id"}
触发时机离开较长时间(≥30 分钟)后回来发消息
ZACPv0.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"]
grokgrok 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\"}"

2. 根因分析

2.1 触发: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:4117:56:27
#3 敦煌飞天出图19:00:07≈19:3020:10:40 / 20:10:51

进程侧也吻合:报错发生时刻 grok agent 才刚被拉起(ps 显示 agent 启动于 20:10, 父进程是 zacp),说明发消息时 ZACP 起了一个全新 agent 进程,而它内存里当然没有旧 session。

2.2 真凶:失效判定漏判 grok 的报错措辞

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,所以从来没暴露过这个洞:

判定里「要求 id 共现」本意是防误伤(例如 unsupported session config option: foo 这种 参数类错误不能当会话失效处理,否则会静默重建会话、丢上下文)。grok 的固定短语 unknown session id 语义唯一、无歧义,单独放行是安全的。

2.3 附带发现: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)。


3. 修复内容

3.1 补丁(6 行逻辑 + 注释)

    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)。

3.2 构建(本机首次需要装工具链)

上游 go.mod 要求 go 1.25.7,系统 apt 只有 1.22,且前端构建强制用 bun (scripts/build.sh 会直接报错退出)。本次已装好并留在本机:

组件位置来源(本网可达的镜像)
Go 1.25.7~/.local/go1.25.7https://mirrors.aliyun.com/golang/go1.25.7.linux-amd64.tar.gz(22 MB/s)
bun 1.4.2~/.bun/bin/bunhttps://registry.npmmirror.com/-/binary/bun/bun-v1.4.2/bun-linux-x64.zip
前端依赖frontend/node_modulesbun 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。

3.3 部署(已执行,保留回滚)

~/.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

3.4 配置调整(~/.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 不走代理
]

3.5 顺手补的可靠性

pm2 save 之前没有包含 zacp(pm2-zyw.service 是 enabled+active,但 dump 里只有 deepseek-harness)→ 重启机器后 ZACP 不会自动回来。已执行 pm2 save, 现在 dump 内进程为 ['deepseek-harness', 'zacp']。


4. 验证记录(全部实测通过)

#验证项方法结果
1判定函数go test ./internal/acp/manager/ -run TestIsUnknownSessionErr8 个子用例全 PASS(含 grok 正例 + 3 个反例不误判)
2grok 是否支持 session/load直接对 grok 跑 ACP 握手探测支持:session/load 返回正常,并把历史以 isReplay:true 全量回放
3REST 路径自愈建测试会话 → 提问记暗号 → kill 掉 grok agent → 再问暗号第 1 轮 已记住(14.2s);杀 agent(pid 2829294) 后第 2 轮回 紫电青霜(8.5s),新 agent pid 2830108 → 上下文经 session/load 完整保留
4WS 路径(你实际用的那条)再杀掉 agent → 直连 ws://127.0.0.1:8680/api/v1/ws 发 promptturn.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 原样保留。


5. 回滚与后续升级

回滚(30 秒)

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

⚠️ 以后如果升级 ZACP(install.sh)

官方 install.sh 会把 ~/.local/bin/zacp 重新指向官方新版——补丁会丢, 「离开久了再发消息」的报错会回来(本补丁尚未提交上游)。届时二选一:

  1. 重打补丁:bash rebuild-zacp-grokfix.sh(会把 VER 改成新版号),或
  2. 把 upstream-fix/ 里的补丁提给 helloxz/zacp(这个洞对任何「不适配 grok 措辞」的 判定都成立,属于通用修复)。

6. 附件清单

文件说明
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 + 已打补丁)。


7. 给后续排查的一句话备忘

ZACP 里 unknown session id 这类「agent 侧会话失效」错误,本应被 IsUnknownSessionErr 捕获并自动 session/load 恢复;它对错误文本的要求是 「含 session」+「含 ≥8 位 id」。任何不带 session id 的措辞都会漏判, 表现就是离开一段时间后发消息直接报错。新接 agent 若报错里没有 id, 就该在 manager.go 的白名单/放行分支里补一条。

下载此文件