Files
cc-web/.planning/ccweb-unrouted-notification-fix/findings.md

51 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 调查结论
## 2026-07-12 根因证据
- `ccweb` PID 1606383 持续占满单个 CPU 核心,RSS 约 130 MiB,系统可用内存约 4.4 GiB。
- `curl http://127.0.0.1:8002/` 能立即建立 TCP,但 8–10 秒无响应,符合事件循环阻塞而非端口未监听。
- `logs/process.log` 每约 0.6 秒出现 `codex_app_notification_unrouted`,来源主要是三个 Codex App threadId。
- 三个 threadId 均存在于当前 `sessions/*-run/codexapp-state.json`,却未恢复进内存路由映射。
- `findCodexAppRouteByRuntime` 在内存命中失败后调用 `adoptCodexAppUnroutedTurn`。
- `findCodexAppSessionByThreadId` 对每条未路由通知执行 `readdirSync(SESSIONS_DIR)`,再逐个 `loadSession`;当前 113 个会话 JSON 合计约 61.3 MiB。
- 高频 delta × 同步全量扫描导致 Node 主线程单核 100%,所有 HTTP 请求饥饿。
- PM2 error log 中的 V8 OOM 修改时间为 2026-06-15,是历史故障,不是当前直接原因。
- 当未路由子线程通知停止后,未部署补丁的旧进程 CPU 从单核 100% 降至 0–2%,本地首页恢复为 HTTP 200/约 2.3 ms;这证明故障与通知风暴强相关,而非持续内存不足或端口问题。
## 工作区约束
- 用户已有修改:`public/app.js`、`public/style.css`、`scripts/regression.js`。
- 现有未跟踪计划/任务目录属于其他工作,不得覆盖或删除。
- `.trellis/.current-task` 当前指向 `00-bootstrap-guidelines`,另有 `07-11-subagent-card-metadata` 进行中;不得切换共享 current-task,以免干扰并发会话。
- 项目要求重启前检查运行会话;只有除当前会话外没有其他 running 会话时才能重启。
- 当前磁盘运行态检查只发现本对话 `4b9700f7-...` 的一个 `codexapp-state.json`,状态为 `running`;没有发现其他会话 run 目录或 classic PID 文件。最终重启前仍需再次复核。
- 重启前通过运行中 ccweb 的内部 MCP `ccweb_list_conversations` 检查:仅当前对话为 `running`,其余全部为 `idle`,满足项目重启条件。
## 部署验证
- 执行 `pm2 restart ccweb --update-env` 成功,新 PID 为 1802039,PM2 restart 计数从 60 增至 61。
- 启动日志出现 `ccweb_mcp_child_threads_recovered`,`restored=3`,证明真实恢复文件中的三个子线程路由被重建。
- 连续 5 次本地 HTTP 探针均返回 200,后续最终探针约 3.6 ms。
- 新进程 RSS 约 64 MiB,事件循环 P95 约 1.5 ms,短时 CPU 回落至 0–12%,没有持续占满单核。
- 重启后的日志窗口仅包含 recovery/server_start 事件,没有新的 `codex_app_notification_unrouted` 风暴。
## 项目上下文
- Trellis 将项目识别为单仓库,规范层为 backend/frontend;本次只涉及 backend。
- codebase-memory 项目 `home-cc-web` 索引状态为 `ready`,当前 3091 个节点、7446 条边。
- 独立计划审查已通过;建议在实施记录中明确测试入口与重启检查。
- 项目测试入口为 `npm run regression`(实际执行 `node scripts/regression.js`),服务入口为 `node server.js`。
- backend 规范目录当前仍是模板状态,没有额外项目特定限制;本次重点遵守现有结构化 `plog`、同步 I/O 热路径规避和回归脚本惯例。
- 预计修改位置:`recoverCodexAppTurnState`、`findCodexAppSessionByThreadId`、`adoptCodexAppUnroutedTurn`、`handleCodexAppNotification`,以及独立/现有回归脚本。
- 当前 `codexapp-state.json` 只持久化父线程 entry;子线程 ID 仅间接存在于 `toolCalls[*].input.agentThreadId` 等协作工具数据中,`recoverCodexAppTurnState` 没有重建 `ccwebMcpChildThreads`。
- `findCodexAppRouteByRuntime` 当前先调用父线程磁盘收养逻辑,再检查 `ccwebMcpChildThreads`;对子线程通知会先触发昂贵的父会话全盘扫描,路由顺序本身也需要调整。
- 现有 `scripts/regression.js` 含用户未提交的“子代理卡片元数据”测试,修复必须追加且保留这些改动。
- 真实恢复文件中,协作子线程以 `toolCalls[].name/kind = subAgentActivity` 持久化;`input.agentThreadId` 是路由键,`input.agentPath` 可作为恢复标签,`input.kind` 表示 started/interacted 等活动。
## HAPI 对比
- HAPI 的“归档”主要是会话生命周期操作:将 active session 断开并把 `metadata.lifecycleState` 设为 `archived`,支持后续 reopen;它不是专门为解决 ccweb 本次每条通知全盘扫描而设计。
- HAPI 的归档确实能减少活动会话集合和实时订阅压力,但历史记录仍保留在存储/缓存层,因此只有配合索引化 SessionCache/SyncEngine 才能避免热路径扫描。
- 对 ccweb 而言,未来增加归档有产品和容量管理价值,但不能替代当前 threadId 路由索引、恢复映射和负缓存修复。
- 源码确认 HAPI `archiveSession` 调用 `rpcGateway.killSession` 后进入 `handleSessionEnd`;Hub 存储基于 `bun:sqlite`,`SessionCache` 维护内存态并以 `active` 作为权威运行标志。