Skip to content

[code-review] MCPManager.Close kills the stdio clients but never unregisters their tools from the Handler-shared registry (the plugin path has the correct Unregister loop; #751's own comment says close prevents tools lingering but only fixed the process leak), and adapter RegisterTools silently skips on name collision - so after closing a session, any later session reusing the same server name (Zed sends MCPServers per tab) gets zero tools while dead-session tools stay exposed; session/load top-level path skips SetSaveDir unlike its three sibling registration sites, so Save writes <id>.json into the process CWD and the real store goes stale; process_other.go tags !unix but uses Windows-only syscall fields #1477

Description

@topcheer

文件行号

internal/acp/mcp_bridge.go L81-91(Close() 只关 clients 清两个 slice——无任何 registry.Unregister;Adapter 亦无反注册方法——grep func (a *Adapter) 仅 5 个方法)+ handler.go L49/L305/L348/L980(toolRegistry 为 Handler 级共享字段,同 ACP 连接所有 session 的 agent loop 共读);tool/tool.go L118-126(Register 重名报错)+ mcp/adapter.go L71-74("name collision is non-fatal" debug.Log 后 skip——注册方零感知)+ handler.go L703-707(session/close 调 Close)+ L972-979(#751 注释原文自称 Close 防 "its tools linger in the shared registry"——半吊子修复自证:只修了进程泄漏一半);handler.go L500-515(顶层 LoadSession 成功后只注册两 map,缺 session.SetSaveDir(sessionDir))vs 三对照点(L296 session/new 有 ✅ / L645 workspace 扫描有 ✅ / 顶层 ❌)+ L628-632(注释自证同款 bug 机理只修了扫描分支)+ agent_loop.go L186/387(Save(SaveDir()) 无空守卫)+ session.go L174(Join("",id) 相对路径写 CWD);process_other.go L1(//go:build !unix)+ L28-31(CreationFlags/HideWindow/CREATE_NEW_PROCESS_GROUP Windows 专属)

问题

案 A(High,跨 session 永久失效):MCPManager.Close 不反注册工具 + 重名静默 skip——同名 MCP server 的后续 session 全部瘫痪

  • 触发链:session A session/newMCPServers:[{name:"github"}] → mcp__github__* 入共享 registry → A close(client 死,工具残留)→ session B 同名 github → RegisterTools 逐个撞名 → adapter skip → B 的健康 client 零工具注册、A 的死工具原样暴露——调用即 "connection closed";即使 B 用不同 server 名,A 的死工具仍作为幻觉可用工具暴露给 B
  • 多 session 客户端(Zed 每开 tab 传一次 MCPServers)是标准用法;仅重启 ACP 连接可解;Close 错误被 debug.Log 吞
  • 对照组:internal/plugin/mcp_loader.go:532 有正确的 m.registry.Unregister(tn)——plugin 路径做了,acp 路径没做;[code-review] ACP session MCP servers closed after first prompt instead of session close - MCP tools dead on prompt #2 #751 注释明说 tools linger 但修复只覆盖进程泄漏
    :Close 先收集 ToolNames() 再逐个 Unregister;同 server 名已存在视为替换而非 skip。

案 B(Med,写错位置+stale):session/load 顶层路径漏 SetSaveDir——Save("") 把会话写进进程 CWD、正式存储永远 stale

  • 顶层 <id>.json(旧布局遗留 + cwd 为空的历史会话也落顶层)→ load 命中 L500 → prompt → agent_loop.go:387 Save("") → CWD 创建 <id>.json(daemon 场景可能是 / 或家目录——污染+信息泄漏);compaction checkpoint(L186)同理丢失——重载回退压缩前历史
  • 注释自证同款 bug 机理但只修了 workspace 扫描分支;handler 侧 prompt 收尾 defer(L435-448)有空守卫所以日常历史大体保留
    :L508-516 补 SetSaveDir;session.go Save 对 dir=="" 直接报错(根因防御)。

案 C(Low,降级——代码卫生):process_other.go !unix tag 含 Windows 专属 syscall 字段

  • plan9/js/wasip1 编译必炸(正确 tag 应为 windows);复核降级:交叉编译实证——项目在 plan9/js/wasip1 早已因 internal/hooks(setProcessGroupKill)/internal/cron(acquireMigrationLock)/internal/tmux(syscall.Flock)等多个依赖包无法构建,这些平台不在支持矩阵(README/CI 仅 darwin/linux/windows);windows/amd64 构建干净通过
    :tag 改 windows(防未来移植踩雷)。

干净项与附注

output_tail.go 整文件无问题。process_unix.go:Pdeathsig 缺失为与 mcp 共有的已知取舍(仅记录)。session.go:prompt 并发互斥(TryBeginRun CAS+EndRun panic 释放)/cancel 全持锁/AddMessage 锁内/LoadSession 不恢复运行态字段/原子写均无问题;Save() 双锁 TOCTOU(check-then-act 窗口——现调用方串行为潜伏 LOW)与 resume 换 servers 后落盘旧清单(MCPServers 不同步,LOW)为附注。mcp_bridge:ConnectServers 部分失败保留成功者/connectServer 错误路径清理/无缓存失效问题均无碍。

严重程度

High + Med + Low


独立复核确认:Close 原文与 Adapter 方法全集 grep、共享 registry 三引用点、#751 注释原文、plugin/mcp_loader.go:532 对照组、撞名 skip 链、三注册点对照与 L628-632 注释自证、SaveDir 消费点无守卫、Join("",id) 机理、四 GOOS 交叉编译矩阵(windows 过/其余依赖先炸)。修正初审一处不实:全仓 Unregister 调用实有 ~49 处,核心事实是 acp/MCP 路径零反注册

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions