Zed 无法打开 opencode Agent:Internal error service: directory 排查实录
by Gralliry
问题现象
在 Zed 里点击 opencode Agent,线程一直无法打开,顶部弹出报错:
Internal error: OpenCode service failure: {
"service": "directory"
}
注意最后那个 "service": "directory"——这是定位问题的关键线索。
而终端里 opencode 用得好好的,只是 Zed 的 agent 面板起不来。
环境
- Windows 11
- Zed(通过扩展自动拉起的 opencode 1.18.15)
- opencode 全局配置在
~/.config/opencode/opencode.json - 系统开着 Clash Verge(verge-mihomo),系统代理
127.0.0.1:7890
第一轮排查:网上资料
这类报错在 GitHub 上并不罕见,先搜了一下相关 issue:
| 链接 | 内容 |
|---|---|
| issue #31091 | ACP session/new 失败,service: "directory",指向 loadDirectorySnapshot() |
| issue #17285 | Windows 下 ACP session/new 返回 Internal error |
| PR #31096 | 根因:设置了 http_proxy/https_proxy 环境变量时,SDK 客户端连 127.0.0.1 的请求被代理劫持,代理转发不了 loopback,导致内部调用全挂 |
参考的结论是:代理环境变量 会导致这个错误。但我的机器上 http_proxy/https_proxy 环境变量(Machine/User 级)都是空的,而且 Clash 明明设置了 localhost 直连,为什么会挂?——这个疑问留到了后面。
复现:手搓一个 ACP 握手
Zed 和 opencode 之间走的是 ACP(Agent Client Protocol)——一个基于 stdio 的 JSON-RPC 协议。不借助 Zed,我直接用管道喂两条 JSON 给 opencode acp,就能精确复现:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientInfo":{"name":"test","version":"1.0"}}}
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/path/to/project","mcpServers":[]}}
结果:
initialize: ✅ 返回正常(版本、能力、模型列表)
session/new: ❌ {"code":-32603,"message":"Internal error: OpenCode service failure","data":{"service":"directory"}}
initialize 成功、session/new 失败——和 Zed 里的表现完全一致。
从 opencode 源码看,session/new 会调用 loadDirectorySnapshot(),它对内部 HTTP 服务并发发起 5 个请求:
/config/providers/app/agents/command/list/app/skills/config
任何一个失败,整个快照加载就抛 service: "directory"。
关键一步:把「服务器」和「客户端」分开测
服务器端:全绿
先直接探测正在运行的 ACP 内部 HTTP 服务(监听 127.0.0.1:4096):
GET /config/providers → 200 (337KB)
GET /app/agents → 200
GET /command/list → 200
GET /app/skills → 200
GET /config → 200
服务器完全健康。 那问题只可能出在客户端那一侧。
客户端:连对地址了吗?
opencode 的 SDK 客户端会用配置里的 server.hostname 来拼内部 API 的 URL。而我的全局配置里写着:
"server": {
"hostname": "0.0.0.0", // ← 问题就在这里
"port": 4096
}
对比一下客户端连两种地址的结果:
GET http://127.0.0.1:4096/config/providers → 200 ✅
GET http://0.0.0.0:4096/config/providers → 502 ❌
真相大白。
根因
server.hostname: "0.0.0.0" 是「服务器监听地址」,服务器绑在 0.0.0.0 上没问题(任何本机地址都能访问到它)。但 SDK 客户端也拿它当目标地址去连,于是发出 http://0.0.0.0:4096/... 的请求。
0.0.0.0 不是一个合法的目标 IP,而且它不是 loopback——所以:
- 系统代理不会把它当作 localhost 直连,请求被交给 Clash → Clash 转发不了 → 502;
- 即使没开代理,客户端直连
0.0.0.0也连不上。
5 个内部调用全部失败 → loadDirectorySnapshot 抛错 → Zed 报 service: "directory"。
这也解释了为什么「Clash 设置 localhost 直连」没用——目标地址是 0.0.0.0,压根不匹配 localhost 的绕过规则。
我最初以为根因是代理,复现测试也确实证明「设置
http_proxy环境变量 → 必挂」。但用户机器上根本没有代理环境变量。真正的一击是:配置里把 hostname 写成0.0.0.0。代理只是让失败以 502 的形式暴露出来而已。
修复
把 hostname 改成 127.0.0.1:
"server": {
"hostname": "127.0.0.1",
"port": 4096
}
如果你确实需要远程访问 opencode 服务,可以改成你的局域网 IP,而不是
0.0.0.0;但仅本机使用的话,127.0.0.1是最稳的。
顺带清理:由于之前多次启动失败,残留的 opencode acp 进程占着 4096 端口,先杀掉旧进程再让 Zed 重新拉起 agent,避免端口冲突。
验证
改完配置、清掉旧进程后,用之前那份 ACP 握手脚本在真实项目里再跑一次:
initialize: ✅
session/new: ✅ 返回 sessionId + 完整模型列表
Zed 里重开一个 opencode 线程,模型能正常选择、消息能正常发出,问题解决。
补充:另一种触发方式
同一个报错还有另一个独立触发条件,就是 GitHub 上大家讨论最多的那个:进程环境里设置了 http_proxy/https_proxy。此时 SDK 客户端连 127.0.0.1 的请求也会被显式地丢给代理,而代理转发不了 loopback → 同样的 service: "directory"。
如果你属于这种情况,给 ACP 进程加上环境变量即可:
"agent_servers": {
"opencode": {
"type": "custom",
"command": "opencode",
"args": ["acp"],
"env": {
"NO_PROXY": "127.0.0.1,localhost"
}
}
}
相关修复 PR #31096 至今未合入,所以目前(1.18.x)这个问题还需要在用户侧规避。
小结
这次排查收获最大的是方法,而不是答案本身:
- 用报错里的 service 名锁定代码路径——
service: "directory"直接指向loadDirectorySnapshot()。 - 绕开 UI,用原始协议复现——手搓 ACP 的 JSON-RPC 握手,几秒钟就能把问题从「Zed 的 bug」里摘出来。
- 把「服务器」和「客户端」拆开测——服务器端 5 个端点全绿,就证明是客户端连错地址,而不是服务不可用。
- 警惕「配置既能当监听地址、又被客户端当目标地址」的双重语义——
server.hostname就是这样一个坑。