目录

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-04venv/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。

时间线拼起来:

  1. 10 月中旬某次更新把 updater 换成新版(可执行入口从 venv/bin/hermes 变成 .hermes/bin/hermes,并引入 channel 体系)。
  2. 新版对"官方源码安装"默认 stable,而我的 checkout 实际跑在 main、且领先 stable。
  3. 按钮一按 = --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 到底什么区别

频道跟什么什么时候能「立即更新」特点
maingit main 分支尖端有人 push 新 commit 就能拉滚动、最快、dev(偶尔翻车)
stable已发布的 release 标签(干净 vX.Y.Z)只有发新 release 才动人工挑过、慢、稳

以后会不会又变回 stable?

不会自动变。 因为 channel: main 已经持久化写进 config。updater 每次先读这条记录,有记录就用记录(main),只有"查不到记录"才落默认(stable)。装机 id 是安装路径的 sha16,只要安装目录不动,这条记录就一直生效。

只有这三种情况会让它"变回”,且都不是自动的:

  1. 你手动跑了 hermes update --set-channel stable;
  2. 安装目录被搬走(路径变 → 装机 id 变 → 记录对不上 → 落默认);
  3. 官方退役 main 频道(main 是 dev 主轨,基本不会)。

随时自检一条命令:

grep -A2 '4913f748c22f9821' ~/.hermes/config.yaml   # 看到 channel: main 就都对

哪天再冒更新报错,先跑这条;channel 若不是 main,就说明被上面 3 种之一改掉了。

经验教训

  1. “报错"不等于"崩了”。 先读日志分清是失败还是保护拦截。这次是防降级保护正常拦下,代码一行没动。
  2. 版本先后用 git 核实,别靠版本号直觉。 v0.22.1+66 和 v0.22.2 谁新,必须 git merge-base / git describe 看事实,不能靠"听起来"。
  3. channel(频道)和 branch(分支)是两套东西。 桌面按钮传哪个决定一切;搞错频道比任何依赖问题都隐蔽。
  4. 定位顺序:先读对日志(desktop-update-handoff.log),再核实事实(git),最后改最小动作(--set-channel)。 全程没重启无关服务、没清依赖。