夜雨聆风学习资料网

ARTICLE · 1162121

用 openclaw config set 修好心跳静默空转 — OpenClaw 投递路由排障实战

用 openclaw config set 修好心跳静默空转 — OpenClaw 投递路由排障实战

现象:任务在跑,消息不来

升级 OpenClaw 之后,agent 的心跳(heartbeat)看起来一切正常:每 30 分钟醒一次,该查的查,该写的写。但操作者的微信里,一条心跳产出都没收到过。

先别猜,直接读状态:

openclaw status
openclaw cron get <heartbeat-job-id>

openclaw status 的 Heartbeat 行后面跟着一句提示:waiting for delivery route(等待投递路由)。cron get 里的关键字段是:

  • state.lastRunStatus
    :任务本身跑没跑
  • lastDeliveryStatus: not-requested
    :产出根本没有被请求投递
  • consecutiveSkipped
     / lastError:辅助判断

翻译成人话:任务活着,但它写出来的东西没有任何地方可去。这不是心跳逻辑坏了,是投递路由丢了。

第一步:分清三种「没收到」

拿到状态先对号入座,三种表现根因完全不同:

  1. skipped: requests-in-flight
     —— 上一轮还在跑,心跳主动让路。正常,自愈,不用管。
  1. lastDeliveryStatus: not-requested
     + waiting for delivery route —— 没有可解析的 owner 路由。这是配置问题,本文主角。
  1. 某个 cron 任务显示 announce → last (last -> no route, will fail-closed) —— 建任务时没加 --no-deliver,又想走「最近会话」投递,但最近会话无路由,直接失败关闭。要么补显式 --channel/--to,要么重建时加 --no-deliver。

路由为什么会丢

本次事故的背景:实例从 root 迁到普通用户 admin 后重建了配置。新实例的 openclaw.json 里,commands 和 heartbeat 相关字段全是 null——旧实例里配好的 owner 身份和投递目标,一个都没跟过来。

迁移会搬工作区、搬记忆、搬 crontab,但很容易漏掉藏在配置深处的这几行。心跳于是变成哑巴:照样按时醒,照样干活,产出全部蒸发。

第二步:从活的会话里取身份,别从记忆里抄

修复需要两个身份信息:频道插件 id 和操作者 user id。不要凭记忆或从别的频道复用,当前会话的路由上下文(origin、active.to、会话状态 JSON 里的 accountId)带着精确值。

拿到之后注意拼写差异,这是最容易翻车的地方:

  • commands.ownerAllowFrom
     的条目格式是 <channel-id>:<user-id>(冒号连接)
  • heartbeat.to
     只接受裸的 <user-id>
  • heartbeat.target
     接受 owner、last、none 或频道 id——永远不要塞 channel:user 这种组合串

第三步:两条 config set 修复

# owner 白名单(注意内层双引号,--strict-json 保证存成 JSON 数组)
openclaw config set commands.ownerAllowFrom \
  '["openclaw-weixin:o9cq80-****@im.wechat"]' --strict-json

# 显式投递路由
openclaw config set agents.defaults.heartbeat.target '"openclaw-weixin"'
openclaw config set agents.defaults.heartbeat.to '"o9cq80-****@im.wechat"'

(user id 已打码;config set 本身不需要重启 gateway。)

关键教训:只设 ownerAllowFrom 不够。 实际恢复过程中,设完白名单,status 依然提示 waiting for delivery route;补上显式 target + to 之后才解析成功。根因没有完全查实——可能是该频道插件对隐式 owner 目标不按 channel:user 拼写解析。工程结论:把 ownerAllowFrom 当命令授权设置,把显式 target+to 当投递修复,两件事分开做,别指望一条顶两条。

第四步:验证,别等下一次心跳干着急

两层验收,缺一不可:

openclaw status   # Heartbeat 行只剩节奏「30m (main)」,不再带路由提示 = 一级通过
openclaw cron get <id>   # 下一轮跑完后,lastDeliveryStatus 不再是 not-requested = 二级通过

修复后本机实测 openclaw status 的 Heartbeat 行:

│ Heartbeat            │ 30m (main)     │

干净了,没有 waiting for delivery route 尾巴。二级验证要等下一个 30 分钟周期,看 lastDeliveryStatus 变化——「路由解析成功」和「消息真的送达」是两级,别混为一谈。

顺手排掉的伴生坑

同一晚还踩了两个相关的:

  • openclaw cron add 默认 announce → last
    :纯后台任务(比如每日发文、备份演练)根本不需要投递,不加 --no-deliver 就会在无路由时 fail-closed,日志里堆一串失败。我先建了两条废任务,删掉重建才干净。
  • openclaw doctor --fix 不能在 gateway 进程树内跑
    :让 agent 用 exec 调它会被拒,这类自愈命令留给操作者在终端手动执行。

节律事实备忘

排障时顺带核实的几条机制,值得记下来:

  • 定时心跳依赖 automations;cron.enabled: false 时心跳完全不会跑。
  • heartbeat.every: "0m"
     只关掉周期性 tick,事件驱动的唤醒(比如后台 exec 完成通知)仍然会触发。
  • 改心跳配置要改 agents.defaults.heartbeat 或 agents.entries.<id>.heartbeat,永远别直接改系统托管的那条 automation job——gateway 会用配置反向重写它,手改必被覆盖。

实战踩坑提醒

  1. 「任务在跑」≠「消息能到」
    :排查投递问题先看 lastDeliveryStatus,不是 lastRunStatus。
  1. 身份串拼写因字段而异
    :ownerAllowFrom 带频道前缀,heartbeat.to 不带,target 不收组合串。
  1. 白名单不是投递修复
    :两个都要设,只设一个会卡在 waiting for delivery route。
  1. 迁移检查清单加一项
    :搬工作区之外,openclaw config get agents.defaults.heartbeat 和 commands.ownerAllowFrom 也要核对,配置深处的 null 不会报错,只会静默。
  1. 纯后台 cron 一律 --no-deliver
    ,别给调度器留 fail-closed 的雷。

小结

心跳静默空转的完整修复链:openclaw status + cron get 读状态定位到路由缺失 → 从活会话取准确身份串 → ownerAllowFrom(授权)与 heartbeat.target/to(投递)分开设置 → status 行变干净、下一轮 lastDeliveryStatus 脱离 not-requested,两级验收。整个过程不到十分钟,但「只设白名单不生效」这一步,不看状态字段光靠猜,能卡一晚上。


关注公众号,获取更多 OpenClaw 实操技巧

相关学习资料