OpenCode 更新卡住?Bun IPv6 问题分析
问题现象
OpenCode 启动时会检查全局包的更新,触发 bun install -g opencode-ai@latest。在某次更新中,该命令在 “Resolving dependencies” 阶段卡死。开启 --verbose 后的输出:
|
|
HTTP 响应头已返回(200 OK,Content-Length 35MB),但 body 数据始终未能完整接收。同一 URL 使用 curl 下载正常,约 20 秒完成 35MB。
此问题在 OpenCode 社区中并非个例。自 2025 年 11 月起已有多位用户报告相同症状:OpenCode 启动时 bun add 卡死,程序无响应(#4682)。有用户排查后发现 bun 在收到 HTTP 304 缓存响应后陷入等待,也有用户直接将根因指向 IPv6——“disable ipv6 or add –no-cache when adding bun appears to be the simplest solution”。该 issue 有 28 条讨论,最终因 90 天无活动被 bot 关闭,而非代码修复。
排查过程
网络层
通过 lsof -i -nP 查看 bun 进程的 TCP 连接(-n 禁用 DNS 反查,-P 不转换端口名):
|
|
连接走的是 IPv6,目标地址 2606:4700::6810:422 属于 Cloudflare。该 IPv6 路径质量较差:
|
|
RTT 约 334ms。对比 curl 的 -4 和 -6 强制模式:
|
|
IPv4 路径 1.6 MB/s,IPv6 路径仅 56 KB/s,差距约 30 倍。
进程层
ps aux | grep opencode 显示有多个 OpenCode 实例和 bun install 进程共存:
|
|
两个 bun install 进程同时竞争 /Users/zmz/.bun/install/global/bun.lock,而各自的 IPv6 连接均在低速下载。即使某一进程下载完成,也无法写入 lockfile——锁被另一个进程持有。
环境层
当前网络环境(macOS, Wi-Fi):
|
|
DNS 解析 registry.npmjs.org 同时返回 AAAA(IPv6)和 A(IPv4)记录。Bun 按 RFC 6724 地址选择规则优先走 IPv6,落入慢速路径。
关闭 Wi-Fi 的 IPv6 后,bun install 成功:
|
|
根因分析
问题链涉及三个层面:
-
IPv6 路由质量差:运营商分配的 IPv6 到达 Cloudflare 的路由延迟高(334ms RTT),吞吐量低(56 KB/s vs 1.6 MB/s for IPv4)。
-
Bun 的 IPv6 fallback 存在盲区:即使 Happy Eyeballs 已实现(v1.1.9),它只作用于 TCP 连接建立阶段——同时发起 IPv6 和 IPv4 握手,先成功者胜出。但在本例中,IPv6 连接成功建立(ESTABLISHED,HTTP 200 响应头已返回),Happy Eyeballs 判定 IPv6 “胜出"后不再干预。问题出在后续的 body 传输阶段:连接虽然建立,但吞吐量仅 56 KB/s,远低于 IPv4 的 1.6 MB/s。Happy Eyeballs 不会因传输慢而切换——它只管连接,不管传输速度。#27337 加上的 10 秒超时同样只覆盖连接握手阶段,不覆盖已建立连接的数据传输。curl 在类似场景下可通过 TCP 重传超时及时发现链路劣化并重试。
-
多进程锁竞争放大问题:多个 OpenCode 实例同时触发
bun install,文件锁竞争使得即使下载接近完成也无法继续。
并非所有环境都会遇到此问题。触发条件:
- 系统启用 IPv6
- 该 IPv6 路径到目标服务器(此处为 Cloudflare 托管的 npm registry)质量差,延迟高或吞吐低(取决于 ISP 路由)
- Bun 的 Happy Eyeballs 未能在传输阶段切换回 IPv4
Bun IPv6 相关 Issue 与修复时间线
此问题在 Bun 项目中有长期跟踪记录。以下是主要 Issue 和 PR 的时间线:
各阶段详细情况
2023.08-09: 问题首次被报告
Issue #4066 是第一个大规模报告,描述了 bun install 下载卡死。社区确认与 IPv6 相关,并提出三个 workaround:
- 禁用 IPv6(Linux:
sysctl net.ipv6.conf.all.disable_ipv6=1,macOS: 系统设置中关闭) - 修改
/etc/gai.conf(Linux),取消注释precedence ::ffff:0:0/96 100,使 IPv4 优先 - 更换 DNS 服务器(部分用户反映从 Cloudflare DNS 切换到其他 DNS 有改善)
Jarred-Sumner 在 #4066 中确认:“This is likely a bug with our dns code.”
2024.01-06: Happy Eyeballs 实现
Jarred-Sumner 在 #10731 中提出了两步修复方案:
- 非阻塞 DNS 解析:macOS 用 libinfo,Windows 用
uv_getaddrinfo,Linux 用线程池,避免 DNS 阻塞事件循环 - Happy Eyeballs v2(RFC 8305):同时发起 IPv6 和 IPv4 连接,选择最先成功者
PR #11206 实现了该方案,在 Bun v1.1.9 中发布。Changelog 说明:
“If Bun receives multiple IP addresses for a single hostname, it will attempt to simultaneously connect to each address, then select the first successful connection.”
需要注意的是,Happy Eyeballs 的覆盖范围仅限 TCP 握手阶段:一旦某个地址的连接成功建立,后续数据传输不再受 Happy Eyeballs 管辖。这意味着如果 IPv6 连接成功但传输缓慢(如本文的 56 KB/s 场景),Happy Eyeballs 不会自动切换回 IPv4。
2025.12-2026.03: 持续回归
Happy Eyeballs 实现后,以下场景仍存在问题:
VPN 虚拟接口 (Issue #25619):Cisco AnyConnect 创建的虚拟接口仅分配 link-local IPv6 地址(fe80::)。link-local 地址无法路由到公网,但 Bun 仍尝试通过它连接,且连接失败后未 fallback。PR #26012 修复:DNS 排序前检测系统是否存在全局 IPv6 源地址(2000::/3),不存在则优先 IPv4。
连接超时缺失 (PR #27337):Bun 底层网络库 uSockets 的 socket timeout 默认值为 255(表示禁用)。当 IPv6 SYN 包被中间路由器丢弃(无 RST 返回)时,连接无限等待,Happy Eyeballs 的 fallback 无法触发。PR #27337 加上 10 秒连接超时,超时后 fallback 到下一个地址。
Tailscale ULA 地址 (Issue #28405):Tailscale 分配 ULA IPv6 地址(fdaa::)。ULA 地址理论可路由,但实际在很多网络环境中不可用,Bun 仍然优先尝试 IPv6。
2026.04: TLS 证书验证问题
Issue #28804(重复 #23735)报告了 macOS 上的 TLS 证书验证失败:
|
|
同一 URL 在 Node、curl、Python 下可正常连接。
根本原因:Bun 使用内置 BoringSSL 进行 TLS 验证,不使用 macOS 的 Security Framework(SecureTransport)。BoringSSL 维护独立的 CA 信任列表,不与系统钥匙链同步。当服务端证书依赖系统钥匙链中的企业 CA 或特定中间 CA 时,Bun 验证失败。
在同一机器上,DeepSeek API(api.deepseek.com)也复现了该问题:
|
|
该 Issue 截至 2026 年 5 月仍未修复。
与 Node.js / curl 的对比
|
|
Node.js 通过 libuv 和 c-ares 处理 DNS 和连接,curl 通过 libcurl。二者均依赖经过长期验证的网络库,无需自己实现 RFC 8305 等协议细节。Bun 选择自行实现网络栈,在常规网络环境下性能更好,但在 IPv6 边界条件下暴露出更多问题。
当前状态与建议
截至 Bun 1.3.14(2026 年 5 月):
| 修复项 | 状态 |
|---|---|
| Happy Eyeballs | 已修复(v1.1.9) |
| link-local IPv6 降权 | 已修复(#26012) |
| 连接超时(10 秒 fallback) | 已修复(#27337) |
| 下载 body 流卡死(本文所述场景) | 未完全修复 |
| macOS TLS 证书验证 | 未修复(#28804/#23735) |
macOS 用户的 workaround:
|
|
或用 npm 替代 bun 安装全局包:
|
|
参考链接
- Issue #4066: bun install takes extremely long time
- Issue #10731: 2 step plan to fix most DNS-related issues in Bun
- PR #11206: Happy Eyeballs implementation
- PR #26012: deprioritize link-local IPv6 addresses
- PR #27337: add connection timeout to prevent IPv6 hangs
- Issue #28405: bun install hangs with deprecated IPv6 ULA
- Issue #28804: fetch fails certificate verification on macOS
- Bun v1.1.9 Release Notes
- BoringSSL in Bun source