Hermes 突然让你过一遍浏览器验证?免费额度的 428 挑战是怎么回事


凌晨两点,你的定时任务在服务器上跑着,用免费额度做一轮检索。日志里没有报错,只有一行打印出来的链接,然后任务安静地失败了。你早上翻日志,完全不知道那个链接是什么、为什么会出现、点不点它。

这不是你的配置出了问题,而是免费额度的入口多了一道检查。10 月 6 日合并的 PR #133824 让 Hermes 能正确处理这条路径,并且——更重要的是——让它不再在没人看着的场合死等。

什么情况下会出现这道检查

免费的匿名推理走的是 POST /api/anonymous/token 做令牌交换。门户那边可以配置规则,对指定 IP 段或国家在发放匿名额度之前先要求过一次浏览器检查。命中规则时,这个接口会返回 HTTP 428,错误码是 challenge_required,并附带一个门户链接。

关键在于:这是门户端的策略,可以随时调整,不需要 Hermes 发版。所以“为什么我上周还好好的”这个问题,答案往往不在你的机器上。

Hermes 只实现一种原语

面对 428,Hermes 实现了叫做 browser-v1 的单一原语,流程就三步:

  1. 打开门户给的 URL;
  2. 轮询 /api/anonymous/challenge/status 直到它清除;
  3. 重新做一次令牌交换。

页面里具体做什么——机器人检测、浏览器内的 proof of work、必要时的人工验证码、以及所有文案——全部属于门户,可以随时改。而是否通过始终由发放方决定:Hermes 永远不会在本地自行判断“这关过了”。

交换请求里会带上一个自报的 client 块(运行界面、版本、平台,以及 capabilities: ["browser-v1"]),User-Agent 形如 hermes-agent/<版本>。

谁负责打开那个窗口

不同界面的行为是不一样的,这点值得记住,因为它是“为什么我什么都没看到”的答案:

  • 桌面端:这个进程自己不开任何东西。它把挑战发布出去(free_tier.challenge 全局事件,以及给稍后连接的客户端看的 free_tier.status.challenge 字段),由渲染进程交给 Electron 主进程,在一个隐藏的、沙箱化的、锁定 origin 的窗口里加载。只有当页面明确要求真人介入时,这个窗口才会显示出来。
  • 终端:打印链接,并且在有图形环境的时候顺手用系统浏览器打开它。
  • 等待发生在锁之外:触发 428 的那次交换已经把异常抛出、离开了 with 块,所以一个慢吞吞的页面不会卡住同机上的其他 profile 或进程。同一个进程里一次只处理一个挑战;另一个线程撞到同一个 428 时会等前一个完成,然后重新交换一次。

什么情况下它绝对不会卡住你

初版实现里最危险的几处,正是这次带上线的修复项。它们都很典型:

  • gateway(Telegram / Discord 等)绝不等待。gateway 会在异步代码里同步解析凭据,初版一旦遇到 428 就会把事件循环停住最多 120 秒——而网关控制台前面根本没有人能去点那个链接。现在的做法是:记录、公告、立刻抛一个可重试的错误。
  • 代理适配器不会握着锁干等。NousPortalAdapter 解析凭据时是持有那把“每个请求都要排队”的锁的,120 秒的停顿会把所有请求一起堵死。现在它按后台调用者处理。
  • 后台读取者不会弹窗口。保活心跳、状态刷新这类调用拿得到挑战,但绝不阻塞、不打印、不打开浏览器。
  • stdio TUI 会明确告诉你。终端 TUI 以前只把 gateway 子进程的 stderr 当日志收着,于是挑战就变成了静默的 120 秒卡死。现在必需的前台挑战会通过 JSON-RPC 公告出来,TUI 打印链接。

required 和 optional 是两回事

还有一种挑战出现在成功的交换响应里,带着 required: false——意思是服务端在测量而不是强制。这种可选挑战只会公告给桌面客户端(好让隐藏窗口提前处理掉),永远不在用户的浏览器里打开,也没有任何东西等它。

真正强制的挑战才会让交互界面进入等待:单次尝试最多等 120 秒,覆盖“真人在解验证码”这种情况,同时不至于把请求挂满票据的整个十分钟。一次失败的公告(比如标准输出出问题)会退回到终端打印链接,而不是把链接弄丢、让运行直接失败。

卡住或失败时会怎样

  • 一次挑战没能清除时,会以可重试的文案结束,并进入一个快速失败的冷却期,不会无限重试。
  • 第二次收到 challenge_required 不会再循环一遍——它会作为一个可重试的错误浮上来,附带说明。这是有意的:环路比失败更糟。
  • 非 428 状态码上出现的 pow_* 错误会保持原来的语义(不再被误当成“所有接口都可重试的服务端错误”),所以注册和升级流程不会被顺带影响。

你该怎么做

如果你的机器是无人值守的(服务器、容器、CI):

  • 看到那个链接,就在你的浏览器里打开它完成检查;令牌是绑给这次交换的,完成后再让 Hermes 重试一次即可。
  • 如果这是常态,考虑把定时任务改成使用已登录的 Nous 账号额度,而不是匿名免费额度——账号路径不会走这道匿名检查。相关排查可以看这篇关于免费搜索路由的说明。
  • 桌面端用户什么都不用做:窗口自己会在后台处理,只有需要你出手时才会露出来。

小结

这道检查不是故障,而是门户端的策略在生效。Hermes 的职责只有三件事:把链接交到你面前、不要在没人看的场合阻塞、不要在本地替发放方下结论。理解了这个分工,你下次在日志里看到那个链接时就知道该干什么了——而不会再花一个早上怀疑自己的配置。

想换一条不依赖免费额度的搜索路径,可以看五种免费搜索渠道的对比。