NO_PROXY 漏了 0.0.0.0:opencode 只在一个 tmux 窗口启动失败的真凶

十月 09, 2026 [AI] #opencode #proxy #tmux #调试

现象

tmux index7 窗口里 opencode -c 启动失败:

Starting background server...
Error: Server process exited with code 1
Error: Managed service port 4096 on 0.0.0.0 is already in use by another process.

其他窗口全部正常。daemon 明明活着(/api/info 1ms 应答 200),客户端却判定服务不存在, spawn 新进程撞端口。

排查:三层反转

第一层:daemon 没病。 进程活着、监听 4096、探测秒回;版本一致排除 replace 链。 日志里确实有 219 条 >2s 的慢请求(多会话并发),最大 8.1s——差点当成"高负载踩穿探测超时", 但它解释不了"只有 index7 失败"的确定性。

第二层:环境对比扑空。 diff 两个窗口 pane 的 /proc/<pid>/environ,只剩 DISPLAY/SSH_CONNECTION 这类 SSH 会话的正常差异。

第三层:换观测点,实锤。 关键动作是直接在 index7 里跑诊断:

$ opencode service status
stopped                      ← 同一个 daemon,这个窗口认为服务不存在!

而 /proc/pid/environ 是进程启动时的快照,shell 事后 export 的变量根本不在里面。 直接问 shell 本体:

$ echo ${http_proxy:-none}
http://192.168.5.244:10808              ← 这个窗口在调试 vless 代理
$ echo $NO_PROXY
localhost,127.0.0.1,::1,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10

NO_PROXY 覆盖了 localhost 和一整圈内网段,唯独没有 0.0.0.0——而 daemon 注册文件里 的 url 恰恰是 http://0.0.0.0:4096(监听地址配了 0.0.0.0 以便 LAN 访问)。

因果链

probe fetch http://0.0.0.0:4096/api/info
  → Bun fetch 尊重 http_proxy,目标主机 "0.0.0.0" 不匹配 NO_PROXY 任何一项
  → 请求丢给 192.168.5.244:10808 的代理入口 → 代理连不了 "0.0.0.0" → 失败
  → ensure 判定服务 missing(daemon 活得好好的)
  → spawn contender → 绑 4096 → EADDRINUSE(撞上健康 daemon)
  → 15s 在位者等待,探测同样走代理失败 → 报错退出

修复验证一行搞定:

$ export no_proxy="0.0.0.0,$no_proxy" NO_PROXY="0.0.0.0,$NO_PROXY"
$ opencode service status
http://0.0.0.0:4096              ← stopped → 健康

教训

  1. /proc/pid/environ 是启动快照——排查"同机不同 shell 行为不一致",env diff 之外必须 问 shell 本体要当前值。这次差点被它带进死胡同。
  2. NO_PROXY 是精确字符串匹配,不是网段直觉——写了一堆 CIDR,偏偏漏了 0.0.0.0 这个特殊地址。代理变量一 export,所有尊重它的 HTTP 客户端(Bun fetch、curl) 都会中招。
  3. opencode 的一个小坑:服务注册 url 用的是绑定地址 0.0.0.0,客户端探测时原样 fetch。归一化成 127.0.0.1 能让所有"localhost 直连"的默认 NO_PROXY 天然免疫—— 值得报个 issue。
  4. 排查顺序的教训:先验证"故障是否可复现、是否确定性",再找机制。日志里的 8s 慢请求 是真实存在的干扰项,但"只在一个窗口失败"这个确定性特征从一开始就指向了本地环境。