51 lines
5.0 KiB
Markdown
51 lines
5.0 KiB
Markdown
# 调查结论
|
||
|
||
## 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` 作为权威运行标志。
|