Files
cc-web/.planning/2026-09-12-ccweb-mcp-create-failure/findings.md

3.5 KiB
Raw Blame History

调查发现

用户现象

  • ccweb MCP 创建对话有时失败。
  • 创建失败后重载 MCP 也挂载不上,导致当前对话不可继续使用。
  • 截图中的 $log-ccweb-title 提示显示:当前会话没有提供 ccweb_list_conversations、ccweb_set_title 或 wiznote_mcp_wiz_* 工具,因此无法获取对话 ID,无法安全写入 /coding 日志。

代码与运行证据

1. MCP 冷启动窗口固定为 10 秒

  • server.js:3528-3572 的 buildCcwebMcpRuntimeConfig() 在 streamable HTTP 和 stdio 两条路径都固定写入 startup_timeout_sec: 10,工具调用窗口为 60 秒。
  • 历史会话 723ffd3f-71fc-42ee-87b6-768836316099、4c0f6be3-b46c-4ec0-b6f5-03c31188b7d8、5ed15712-312c-4d1b-b629-3d3a3c0d06a7 均持久化了:MCP client for ccweb timed out after 10 seconds。
  • 同一批历史记录随后出现 ccweb_list_conversations 60 秒工具调用超时,说明客户端失败后仍可能继续尝试调用失效连接。

2. 创建对话成功不等于目标 MCP 已就绪

  • createMcpConversation() 先通过 createPersistentConversationSession() 写入会话文件,再调用 sendCrossConversationMessage() 投递首条消息。
  • sendCrossConversationMessage() 调用 handleMessage();Codex App 分支的 handleCodexAppMessage() 只登记 active turn 并异步执行 startCodexAppTurn(...).catch(...),立即返回 { ok: true }。
  • 因此创建接口返回的 ok/status=running 只表示“会话已落盘、首轮已开始”,不保证首轮完成,更不保证 ccweb MCP 已 ready。MCP 启动失败会在返回之后发生。

3. 重载状态关联存在 threadId 严格匹配竞态

  • handleReloadMcpApi() 在 markCodexAppMcpReloadPending() 后调用无 thread 参数的 config/mcpServer/reload,并只等待 CODEX_APP_MCP_RELOAD_STATUS_WAIT_MS = 1200 毫秒。
  • codexAppMcpStatusTargetSessionIds() 对 pending 会话要求 statusRecord.threadId === pending.threadId;不一致时直接跳过。
  • logs/process.old.log:6713-6721 中,重载请求针对 019ff161-...,随后上报的是 019fef64-... 与 019fef0f-...,且全部 targetSessions=0。对应会话 b73e4b07-4aaa-43d5-906b-413747a09f4b 最终仍持久化为 ccweb.status=starting/rawStatus=pending。
  • cleanupExpiredCodexAppMcpReloads() 只删除内存 pending 映射,不会把持久化状态从 starting/pending 改成失败或可重试,因此会话在 UI 上长期像“挂载中”。

4. 当前环境可复现“并发启动时序差异”

  • 当前调查会话的 ccweb 最终为 ready,但同一线程的 playwright 在 20 秒后失败,说明多个 MCP 并发启动时不同服务的 ready/fail 到达时间并不一致;固定 10 秒窗口和 1.2 秒重载等待会放大这个时序问题。

已实施修复

  • server.js 和 lib/agent-runtime.js:ccweb MCP 默认启动超时提高到 30 秒,工具超时和 reload 等待窗口支持环境变量覆盖;reload 默认等待 35 秒。
  • server.js:reload pending 记录允许同一 app-server 的全局 reload 通知跨 threadId 关联;等待窗口结束仍未收到 ready/failed/cancelled 时,将持久化状态收敛为 failed 并标记可重试。
  • server.js:Codex App 创建对话结果附带当前 mcpStatus;首条消息投递失败时保留会话 ID、失败阶段和可重试标记,避免“已创建的会话”被误认为完全不存在。
  • scripts/mock-codex-app-server.js、scripts/regression.js:新增无关 threadId 和无最终状态通知的回归场景。