Homebrew `brew update` 卡住问题排查以及解决

记一次 Homebrew brew update 卡住:镜像源、JSON API 与 Git Tags 的排障

环境背景:macOS Apple Silicon,Homebrew 安装在 /opt/homebrew,Shell 为 zsh,网络环境需进行配置优化以访问GitHub等外部资源。

问题现象

执行:

bash 复制代码
brew update

终端只输出了如下信息:

text 复制代码
HOMEBREW_BREW_GIT_REMOTE set: using https://mirrors.aliyun.com/homebrew/brew.git as the Homebrew/brew Git remote.
Fetching objects: 7

之后更新非常慢,甚至看起来像卡死。

当时已经配置了阿里云 Homebrew 镜像:

bash 复制代码
export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/brew.git"
export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.aliyun.com/homebrew/homebrew-core.git"
export HOMEBREW_API_DOMAIN="https://mirrors.aliyun.com/homebrew-bottles/api"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.aliyun.com/homebrew/homebrew-bottles"

后来切到清华 TUNA 后,brew update --debug --verbose 能成功走完 API 更新阶段:

text 复制代码
Checking if we need to fetch formula.jws.json...
Updated formula.jws.json.
Checking if we need to fetch cask.jws.json...
Updated cask.jws.json.
Checking if we need to fetch formula_tap_migrations.jws.json...
Updated formula_tap_migrations.jws.json.
Checking if we need to fetch cask_tap_migrations.jws.json...
Updated cask_tap_migrations.jws.json.
Already up-to-date.

brew --version 仍然停在:

text 复制代码
Homebrew 5.1.0

再次执行普通 brew update,又出现 Git fetch 错误:

text 复制代码
HOMEBREW_BREW_GIT_REMOTE set: using https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git as the Homebrew/brew Git remote.
error: RPC failed; curl 28 Operation too slow. Less than 1000 bytes/sec transferred the last 20 seconds
fatal: protocol error: bad pack header
Already up-to-date.

这个 Already up-to-date 很误导。它不代表 Homebrew 主程序已经更新,只代表后续 update-report 阶段没有更多可报告内容。

先说结论

这次问题不是单纯的"镜像没配好",而是两个问题叠加:

  1. HOMEBREW_API_DOMAIN / HOMEBREW_BOTTLE_DOMAIN 负责 Homebrew JSON API 和 bottle 下载。
  2. HOMEBREW_BREW_GIT_REMOTE 负责 Homebrew 主程序仓库 Homebrew/brew.git 的 Git 更新。

API 镜像切到清华后已经正常;真正让 brew 版本停留在 5.1.0 的原因是:

text 复制代码
Homebrew/brew.git 的 git fetch --force --tags origin 没成功

因此本地没有拿到 5.1.1+6.0.x 等新 tag。Homebrew 的 stable 更新逻辑依赖本地已有 tag:本地最新 tag 是 5.1.0,它就只能继续停在 5.1.0

最终有效方案是:

  • API 和 Bottle 继续走清华镜像;
  • Homebrew 主程序 brew.git 不再走清华/阿里 Git 镜像;
  • brew.git 改回 GitHub;
  • 通过优化网络配置(如使用HTTP/1.1协议)手动刷新 tags;
  • 删除或注释 HOMEBREW_BREW_GIT_REMOTEHOMEBREW_CORE_GIT_REMOTE,避免后续又被强制切回镜像 Git remote。

背景知识:Homebrew 4+ 的更新路径变了

Homebrew 4.0 之后,大多数普通用户不再需要完整克隆 homebrew/corehomebrew/cask 仓库。Homebrew 默认更多依赖 JSON API:

bash 复制代码
HOMEBREW_API_DOMAIN
HOMEBREW_BOTTLE_DOMAIN

其中:

  • HOMEBREW_API_DOMAIN:用于下载 formula/cask 元数据,例如 formula.jws.jsoncask.jws.json
  • HOMEBREW_BOTTLE_DOMAIN:用于下载预编译 bottle。
  • HOMEBREW_BREW_GIT_REMOTE:用于更新 Homebrew 自己,也就是 Homebrew/brew.git
  • HOMEBREW_CORE_GIT_REMOTE:用于本地 homebrew/core Git tap;普通用户通常不再需要。

清华 TUNA 文档也明确说明,brew 4.0 之后大部分用户不需要再克隆 homebrew/core,只需要设置 API 和 bottle 镜像即可。

排障过程

1. 确认环境变量是否生效

先看当前 Shell 是否真的拿到了 Homebrew 镜像变量:

bash 复制代码
printenv | grep '^HOMEBREW_'

当时输出显示变量已经生效:

text 复制代码
HOMEBREW_BREW_GIT_REMOTE=https://mirrors.aliyun.com/homebrew/brew.git
HOMEBREW_CORE_GIT_REMOTE=https://mirrors.aliyun.com/homebrew/homebrew-core.git
HOMEBREW_API_DOMAIN=https://mirrors.aliyun.com/homebrew-bottles/api
HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles

所以问题不是 /etc/profile 完全没生效。

2. 确认 Homebrew 自身启动速度正常

bash 复制代码
time brew --version

输出很快:

text 复制代码
Homebrew 5.1.0
brew --version  0.02s user 0.03s system 47% cpu 0.101 total

这排除了 Homebrew 自身启动很慢、Ruby 初始化卡住、基础安装损坏等问题。

3. 确认没有残留 brew / git 进程占用锁

bash 复制代码
ps aux | grep -E '[b]rew|[g]it|[c]url|[r]uby'

没有发现残留的 brew updategit fetchcurl 进程。

因此不是另一个 brew update 进程仍在运行。

4. 单独测试镜像 Git 仓库是否能连通

bash 复制代码
git ls-remote https://mirrors.aliyun.com/homebrew/brew.git HEAD

可以返回:

text 复制代码
01a27a96e15d3bbc02c81b18f95bb1d8f24d490c HEAD

这说明"能列远端 HEAD"不等于"能完整 git fetch --tags"。后者需要传输 pack,对网络、镜像质量及HTTP协议要求更高。

5. 用 debug 日志定位 brew update 卡点

执行:

bash 复制代码
HOMEBREW_DEBUG=1 brew update --verbose 2>&1 | tee /tmp/brew-update-debug.log

日志显示,API 文件从清华 TUNA 下载成功:

text 复制代码
Checking if we need to fetch formula.jws.json...
Updated formula.jws.json.
Checking if we need to fetch cask.jws.json...
Updated cask.jws.json.
Checking if we need to fetch formula_tap_migrations.jws.json...
Updated formula_tap_migrations.jws.json.
Checking if we need to fetch cask_tap_migrations.jws.json...
Updated cask_tap_migrations.jws.json.

但 Homebrew 仍然停在 5.1.0

关键片段是:

text 复制代码
++ git tag --list --sort=-version:refname
+ grep -m 1 '^[0-9]*\.[0-9]*\.[0-9]*$'
+ /Library/Developer/CommandLineTools/usr/bin/git tag --list --sort=-version:refname
+ UPSTREAM_TAG=5.1.0
 REMOTE_REF=refs/tags/5.1.0
 UPSTREAM_BRANCH=stable
 git checkout --force -B stable refs/tags/5.1.0
 git rebase refs/tags/5.1.0
 Current branch stable is up to date.

这说明 Homebrew 是根据本地 tag 决定 stable 版本的。因为本地最新 tag 只有 5.1.0,所以它不会升级到更高版本。

6. 验证本地 tags 的确缺失

bash 复制代码
git -C "$(brew --repo)" tag --list '5.*' --sort=-version:refname | head -10

修复前看到的最高版本是 5.1.0。这就解释了为什么 brew --version 一直不变。

7. 发现 git fetch 真正失败原因

执行普通 brew update 时出现:

text 复制代码
error: RPC failed; curl 28 Operation too slow. Less than 1000 bytes/sec transferred the last 20 seconds
fatal: protocol error: bad pack header

这说明 git fetch 的 HTTP 传输被低速保护中断,或者镜像/HTTP 协议链路中途断包,导致 Git pack 收到不完整数据,进而报 bad pack header

最终修复过程

3. 取消强制使用 Homebrew Git 镜像

bash 复制代码
unset HOMEBREW_BREW_GIT_REMOTE
unset HOMEBREW_CORE_GIT_REMOTE

把 Homebrew 主仓库 remote 改回 GitHub:

bash 复制代码
git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git

4. 强制用 HTTP/1.1 刷新 main 和 tags

bash 复制代码
git -C "$(brew --repo)" \
  -c http.version=HTTP/1.1 \
  fetch --force --tags origin refs/heads/main:refs/remotes/origin/main

这次成功:

text 复制代码
remote: Enumerating objects: 17951, done.
remote: Counting objects: 100% (5036/5036), done.
remote: Compressing objects: 100% (58/58), done.
remote: Total 17951 (delta 5007), reused 4978 (delta 4978), pack-reused 12915 (from 6)
Receiving objects: 100% (17951/17951), 6.64 MiB | 16.66 MiB/s, done.
Resolving deltas: 100% (12675/12675), completed with 1135 local objects.
From https://github.com/Homebrew/brew
   0f7b6e1dd7..01a27a96e1  main       -> origin/main
 * [new tag]               5.1.1      -> 5.1.1
 * [new tag]               5.1.10     -> 5.1.10
 * [new tag]               5.1.11     -> 5.1.11
 * [new tag]               5.1.12     -> 5.1.12
 * [new tag]               5.1.13     -> 5.1.13
 * [new tag]               5.1.14     -> 5.1.14
 * [new tag]               5.1.15     -> 5.1.15
 * [new tag]               6.0.0      -> 6.0.0
 * [new tag]               6.0.1      -> 6.0.1
 * [new tag]               6.0.2      -> 6.0.2

5. 验证 tags 已经补齐

bash 复制代码
git -C "$(brew --repo)" tag --list '5.*' --sort=-version:refname | head -10

输出:

text 复制代码
5.1.15
5.1.14
5.1.13
5.1.12
5.1.11
5.1.10
5.1.9
5.1.8
5.1.7
5.1.6

这时本地已经具备升级条件。

6. 注释镜像 Git remote 配置后,brew update 正常

注释掉:

bash 复制代码
# export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
# export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"

然后在当前 Shell 中执行:

bash 复制代码
unset HOMEBREW_BREW_GIT_REMOTE
unset HOMEBREW_CORE_GIT_REMOTE

确认变量为空:

bash 复制代码
printenv | grep '^HOMEBREW_.*GIT_REMOTE'

无输出即表示已清理。

再次执行:

bash 复制代码
brew update

输出:

text 复制代码
==> Updating Homebrew...
Already up-to-date.

这次不再出现:

text 复制代码
HOMEBREW_BREW_GIT_REMOTE set: using ...
error: RPC failed; curl 28 ...
fatal: protocol error: bad pack header

最终推荐配置

长期不建议保留:

bash 复制代码
export HOMEBREW_BREW_GIT_REMOTE="..."
export HOMEBREW_CORE_GIT_REMOTE="..."

原因:

  1. 它们会让每次 brew update 都强制修改 Git remote。
  2. 本次故障已经证明 Git 镜像链路容易在 fetch --tags 时失败。
  3. Homebrew 4+ 大多数普通用户不再需要本地 homebrew/core Git tap。

推荐只保留 API 和 bottle 镜像:

bash 复制代码
export HOMEBREW_API_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles"

如果希望避免每次安装软件都触发自动更新,可以加:

bash 复制代码
export HOMEBREW_NO_AUTO_UPDATE=1

推荐的日常命令

安装软件时不自动 update:

bash 复制代码
HOMEBREW_NO_AUTO_UPDATE=1 brew install <formula>

手动更新 Homebrew 和 API 元数据:

bash 复制代码
brew update

如果 GitHub 链路偶发不稳定,可尝试调整网络配置并使用 HTTP/1.1:

bash 复制代码
GIT_HTTP_LOW_SPEED_LIMIT=0 \
GIT_HTTP_LOW_SPEED_TIME=999999 \
brew update --verbose

也可以只给 Homebrew 仓库固定 HTTP/1.1:

bash 复制代码
git -C "$(brew --repo)" config http.version HTTP/1.1

常用诊断命令

查看 Homebrew 版本

bash 复制代码
brew --version

查看 Homebrew 相关环境变量

bash 复制代码
printenv | grep '^HOMEBREW_'

查看是否仍设置了 Git remote 镜像变量

bash 复制代码
printenv | grep '^HOMEBREW_.*GIT_REMOTE'

查看 Homebrew 主仓库 remote

bash 复制代码
git -C "$(brew --repo)" remote -v

查看本地最新 stable tags

bash 复制代码
git -C "$(brew --repo)" tag --list --sort=-version:refname | grep -m 10 '^[0-9]*\.[0-9]*\.[0-9]*$'

手动刷新 Homebrew 主仓库 main 和 tags

bash 复制代码
git -C "$(brew --repo)" \
  -c http.version=HTTP/1.1 \
  fetch --force --tags origin refs/heads/main:refs/remotes/origin/main

查看 debug 日志

bash 复制代码
HOMEBREW_DEBUG=1 brew update --verbose 2>&1 | tee /tmp/brew-update-debug.log
tail -80 /tmp/brew-update-debug.log

误区总结

误区 1:git ls-remote 成功就代表 git fetch 没问题

不是。

git ls-remote 只需要列远端引用,数据量很小。git fetch --tags 要下载 pack,容易暴露镜像、HTTP/2、低速中断、运营商链路等问题。

误区 2:Already up-to-date 一定代表 Homebrew 已升级

不是。

这次就出现了:

text 复制代码
Already up-to-date.

但:

bash 复制代码
brew --version

仍然是:

text 复制代码
Homebrew 5.1.0

真正要看的是本地 tags 和 brew --version

误区 3:配置完整镜像变量一定更快

不一定。

对于 Homebrew 4+ 普通用户,最有价值的是:

bash 复制代码
HOMEBREW_API_DOMAIN
HOMEBREW_BOTTLE_DOMAIN

而:

bash 复制代码
HOMEBREW_BREW_GIT_REMOTE
HOMEBREW_CORE_GIT_REMOTE

可能让 brew update 在 Git fetch 阶段失败,尤其是 Git 镜像同步不完整、网络慢、网络环境不稳定时。

根因复盘

本次问题的根因链路是:

  1. 配置了 HOMEBREW_BREW_GIT_REMOTEbrew update 每次都会尝试使用镜像 Git remote 更新 Homebrew/brew.git
  2. 阿里云 Homebrew API 镜像内容较旧,不适合作为当前 Homebrew 5.x 的稳定 API 来源。
  3. 切到清华后,API 下载恢复正常,但 brew.gitfetch --tags 仍在镜像 Git remote 上出现 curl 28 / bad pack header
  4. 因为 fetch --tags 失败,本地最高 stable tag 停留在 5.1.0
  5. Homebrew 根据本地最高 tag 选择 stable 版本,因此 brew --version 一直是 5.1.0
  6. 取消 HOMEBREW_BREW_GIT_REMOTE,把 brew.git remote 改回 GitHub,并通过优化网络配置(如使用HTTP/1.1协议)成功 fetch tags 后,问题解决。

最终落地方案

最终配置应简化为:

bash 复制代码
# Homebrew JSON API 与 bottles 走国内镜像
export HOMEBREW_API_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles"

# 可选:避免安装软件时自动触发 update
export HOMEBREW_NO_AUTO_UPDATE=1

不要长期设置:

bash 复制代码
# 不建议长期设置
# export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git"
# export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"

参考资料

总结

Homebrew 4+ 的日常加速重点是 API 和 bottle 镜像;不要盲目长期设置 HOMEBREW_BREW_GIT_REMOTE。如果 brew update 卡在 Git fetch,先让 Homebrew/brew.git 回到 GitHub,通过优化网络配置(如使用HTTP/1.1协议)成功刷新 tags,再执行 brew update


good day!!!