记一次 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 阶段没有更多可报告内容。
先说结论
这次问题不是单纯的"镜像没配好",而是两个问题叠加:
HOMEBREW_API_DOMAIN/HOMEBREW_BOTTLE_DOMAIN负责 Homebrew JSON API 和 bottle 下载。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_REMOTE、HOMEBREW_CORE_GIT_REMOTE,避免后续又被强制切回镜像 Git remote。
背景知识:Homebrew 4+ 的更新路径变了
Homebrew 4.0 之后,大多数普通用户不再需要完整克隆 homebrew/core 和 homebrew/cask 仓库。Homebrew 默认更多依赖 JSON API:
bash
HOMEBREW_API_DOMAIN
HOMEBREW_BOTTLE_DOMAIN
其中:
HOMEBREW_API_DOMAIN:用于下载 formula/cask 元数据,例如formula.jws.json、cask.jws.json。HOMEBREW_BOTTLE_DOMAIN:用于下载预编译 bottle。HOMEBREW_BREW_GIT_REMOTE:用于更新 Homebrew 自己,也就是Homebrew/brew.git。HOMEBREW_CORE_GIT_REMOTE:用于本地homebrew/coreGit 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 update、git fetch 或 curl 进程。
因此不是另一个 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="..."
原因:
- 它们会让每次
brew update都强制修改 Git remote。 - 本次故障已经证明 Git 镜像链路容易在
fetch --tags时失败。 - Homebrew 4+ 大多数普通用户不再需要本地
homebrew/coreGit 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 镜像同步不完整、网络慢、网络环境不稳定时。
根因复盘
本次问题的根因链路是:
- 配置了
HOMEBREW_BREW_GIT_REMOTE,brew update每次都会尝试使用镜像 Git remote 更新Homebrew/brew.git。 - 阿里云 Homebrew API 镜像内容较旧,不适合作为当前 Homebrew 5.x 的稳定 API 来源。
- 切到清华后,API 下载恢复正常,但
brew.git的fetch --tags仍在镜像 Git remote 上出现curl 28/bad pack header。 - 因为
fetch --tags失败,本地最高 stable tag 停留在5.1.0。 - Homebrew 根据本地最高 tag 选择 stable 版本,因此
brew --version一直是5.1.0。 - 取消
HOMEBREW_BREW_GIT_REMOTE,把brew.gitremote 改回 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 Manpage: https://docs.brew.sh/Manpage
- Homebrew Common Issues: https://docs.brew.sh/Common-Issues
- TUNA Homebrew 帮助: https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/
- TUNA Homebrew Bottles 帮助: https://mirrors.tuna.tsinghua.edu.cn/help/homebrew-bottles/
- Git HTTP 配置文档:
http.lowSpeedLimit/http.lowSpeedTime/http.version
总结
Homebrew 4+ 的日常加速重点是 API 和 bottle 镜像;不要盲目长期设置 HOMEBREW_BREW_GIT_REMOTE。如果 brew update 卡在 Git fetch,先让 Homebrew/brew.git 回到 GitHub,通过优化网络配置(如使用HTTP/1.1协议)成功刷新 tags,再执行 brew update。
good day!!!