Hermes Agent 桌面版「立即更新」突然报降级失败:stable / main 频道误判与修复
一次被误判成『崩了』的自更新,其实是防降级保护在正常拦截;根因是新版 updater 把官方源码安装默认算进了 stable,钉回 main 即愈

Hermes 桌面版「立即更新」突然报降级失败:stable / main 频道误判与修复
家里数字工厂的底座是 Hermes Agent。某天我从右下角点「立即更新」,弹出来的不再是熟悉的平滑滚动,而是一句报错:
✗ Refusing to move this install to d923a5eb487a: that release predates the gateway runtime
these session stores now use. Downgrading is unsupported.
/home/oklife/.hermes/state.db has gateway runtime history.
No update was applied; your install is unchanged.字面上像"更新失败",但最后一句 your install is unchanged 很关键——代码根本没动,是被拦住的。而且之前我一直点这个按钮从没出过事,怎么今天突然报?
这篇把"现象 → 定位 → 根因 → 一条命令修复 → 验证 → 防复发"完整记下来。
一开始我绕了条岔路
第一反应我以为是依赖 / 构建问题(OpenClaw 升级前挂过一次 python-olm),于是先去翻 OpenClaw 日志。翻出来的确是另一个真问题(OpenClaw 记忆向量索引重建时本地 embedding 不可用),但它跟这次报错没关系——那是 openclaw memory status 的独立链路。
提醒一句:“升级报错"这类词,先分清是谁在升级、报在哪条链路,别一上来就清依赖、重启服务。 这次该看的是桌面自更新日志。
读对日志:desktop-update-handoff.log
Hermes 桌面自更新会写一份 handoff 日志:~/.hermes/logs/desktop-update-handoff.log。它记录了每次点按钮实际执行的命令和退出码。把不同时期的命令一对比,入口就出来了:
| 时间 | 「立即更新」实际命令 |
|---|---|
| ≤ 2026-10-04 | venv/bin/hermes update --yes --gateway --keep-stash --branch main |
| 2026-10-17(本次) | .hermes/bin/hermes update --yes --gateway --keep-stash --channel stable |
按钮传的东西,从 --branch main 变成了 --channel stable。这就是全部线索的入口。
用 git 核实版本时间线(别靠直觉)
“v0.22.2 听起来比 v0.22.1+66 新"是错觉,得用 git 事实说话:
本地: git describe → v0.22.1-66-g7bda2a3368 (main 分支, 领先 v0.22.1 tag 66 commit)
stable: d923a5eb487a → "Release 0.22.2 from release/0.22.1"
分叉点: git merge-base → 297552a9
main 侧 +66 commit, stable 侧 +11 commit两者不是包含关系,是在 v0.22.1 处分叉的两条线:main 是 dev 主线(带 66 个 dev commit,含更新的 gateway runtime),stable 的 v0.22.2 是 release 分支上挑出来的 11 个 commit。我的 main 构建在 gateway 运行时时序上领先 stable v0.22.2。
所以走 --channel stable = 让我退到比现在还旧的版本 = 降级。updater 的防降级保护直接拦掉,并明说 install is unchanged。
这次"报错"其实是保护机制在正常工作,不是崩了。
根因:按钮为什么会从 main 切到 stable
翻 updater 源码(hermes_cli/update_channel.py、hermes_cli/source_releases.py),逻辑很清楚:
# 源码型(git)安装的默认频道
def default_channel(root):
...
return CHANNEL_STABLE if _tracks_official_origin(root) else CHANNEL_MAIN也就是:
- 官方仓库的源码安装 → 默认跟
stable - fork / 镜像 → 默认跟
main
我的安装 origin 就是官方仓库 https://github.com/NousResearch/hermes-agent,所以新版 updater 把它算成了 stable。
而 resolve_update_channel 的规则是:config 里写了 update.installs.<装机id>.channel 就用它,没写就落默认。我这边从没写过这条记录,于是落到默认 stable。
时间线拼起来:
- 10 月中旬某次更新把 updater 换成新版(可执行入口从
venv/bin/hermes变成.hermes/bin/hermes,并引入 channel 体系)。 - 新版对"官方源码安装"默认 stable,而我的 checkout 实际跑在 main、且领先 stable。
- 按钮一按 =
--channel stable= 降级 = 被拦。
修复:一条命令钉回 main
源码型安装里,channel 名 main 有专门处理(source_releases.py):
if channel == "main":
# main 就是 git 分支,不读 R2,直接跟 main 尖端
return SourceTarget(channel, channel, repository, branch="main")所以只要把本安装钉到 main 频道,按钮就恢复成"跟 git main 尖端往前滚”:
~/.hermes/hermes-agent/.hermes/bin/hermes update --set-channel main
# 输出: Update channel for 4913f748c22f9821: main它会在 ~/.hermes/config.yaml 里写一条(纯配置写,不拉代码、不重启 gateway):
update:
installs:
4913f748c22f9821: # 安装路径的 sha16
path: /home/oklife/.hermes/hermes-agent
channel: main验证:补拉 1000+ commit 到最新
钉好频道后点「立即更新」,这次是往前拉。先看落后多少:
git fetch origin
origin/main 尖端: 868b9f244f (feat: add deepseek/... 到 OpenRouter 与 Nous 目录)
本地 7bda2a33..origin/main: 落后 1073 个 commit更新完之后:
hermes --version → v0.22.2+1128.g868b9f2 (upstream 868b9f24)
gateway 已重启(新 pid), 桌面显示「正在使用最新版本」v0.22.2+1128 说明 main 已经越过 v0.22.2 发版标签、又往前走了 1128 个 commit——稳稳站在 dev 主线尖端。这次比平时慢了几分钟,纯粹是 1000+ commit 的大跳 + 依赖同步 + 构建 + gateway 重启 + hand-off(自动退出→自动启动),正常。
main 和 stable 到底什么区别
| 频道 | 跟什么 | 什么时候能「立即更新」 | 特点 |
|---|---|---|---|
| main | git main 分支尖端 | 有人 push 新 commit 就能拉 | 滚动、最快、dev(偶尔翻车) |
| stable | 已发布的 release 标签(干净 vX.Y.Z) | 只有发新 release 才动 | 人工挑过、慢、稳 |
以后会不会又变回 stable?
不会自动变。 因为 channel: main 已经持久化写进 config。updater 每次先读这条记录,有记录就用记录(main),只有"查不到记录"才落默认(stable)。装机 id 是安装路径的 sha16,只要安装目录不动,这条记录就一直生效。
只有这三种情况会让它"变回”,且都不是自动的:
- 你手动跑了
hermes update --set-channel stable; - 安装目录被搬走(路径变 → 装机 id 变 → 记录对不上 → 落默认);
- 官方退役 main 频道(main 是 dev 主轨,基本不会)。
随时自检一条命令:
grep -A2 '4913f748c22f9821' ~/.hermes/config.yaml # 看到 channel: main 就都对哪天再冒更新报错,先跑这条;channel 若不是 main,就说明被上面 3 种之一改掉了。
经验教训
- “报错"不等于"崩了”。 先读日志分清是失败还是保护拦截。这次是防降级保护正常拦下,代码一行没动。
- 版本先后用 git 核实,别靠版本号直觉。
v0.22.1+66和v0.22.2谁新,必须git merge-base/git describe看事实,不能靠"听起来"。 - channel(频道)和 branch(分支)是两套东西。 桌面按钮传哪个决定一切;搞错频道比任何依赖问题都隐蔽。
- 定位顺序:先读对日志(
desktop-update-handoff.log),再核实事实(git),最后改最小动作(--set-channel)。 全程没重启无关服务、没清依赖。
梦行志