Margrop
Articles358
Tags594
Categories7

Categories

12类 1password 401 503 6个节点 AC ACP AI AI Coding Assistant AI编程助手 AI辅助 AI辅助编程 AP API Alertmanager AppDaemon Aqara BaiduPCS CC-Switch CI/CD CLI Tools CLI工具 Caddy Claude Code Cloudflare Codex Cookie 认证 Cron D1 DB探针 DB静止 DIY-MINI Date Diagrams.net Diary Docker Docker Compose Efficiency Tools Electerm English FTS5 Gateway Gemini CLI GitHub Actions HA HADashboard Hermes Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Markdown MiniMax Multi-Agent MySQL NAS NRestarts Nginx Node-RED Node.js OOM OpenAI OpenClaw OpenCode OpenResty OpenWrt PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE RPC SOCKS5 SQLite SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VPN VPS WeCom Web WebSocket Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy autofs backup baidupan bash bitwarden boot brew browser by-design caddy2 cdn centos cert certbot charles chat chrome classloader client clone closures cloudflare cmd command commit connected container cron crontab cron任务 cron设计 ctyun dashboard ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump dylib edge exception export fail2ban fallback fallback失效 feign firewall-cmd flow frp frpc frps fuckgfw function fuser gcc gfw git gitea github golang google_gemma-4 gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https iKuai idea image img img2kvm immortalwrt import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jieba jni jnilib jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live loopback-proxy low-code lsof lvm lxc m3u8 mac macos manual mariadb markdown maven md5 microcode mirror model provider modem modules monitor mount mstsc mysql n2n n5105 nas netstat network new-api nfs node node-red nodejs nohup notepad++ npm nssm ntp one-api oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping pip plugin png powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reconnect循环 reflog remote remote desktop renew repo resize retina root route router rule rules running runtime safari sata scipy-notebook scoping scp server server is busy slmgr so socket-proxyd socks source spk spring springboot springfox ss ssh ssl stale stash string supernode svg svn swagger sync synology systemctl systemd systemd unit systemd-socket tap tap-windows tapwindows telecom template terminal tls tmux token token失效 totp trigram tvbox txt ubuntu udisk ui undertow unicode61 uninstall unlocker upgrade uptimeMs url v1探针 v2ray vhd vim vlmcsd vm vmdk web websocket wechat windows with worker wow xiaoya xml yum zip 中国电信 中文搜索 主动追问 云电脑 交换机 人机协作 代理 优化 体检 值班 假阳 假阴 健康检查 光猫 全绿 全量同步 公网IP 内存 内存优化 内网 内网IP 内网渗透 写作 分词 切换 升级 协作 单位混淆 博客 反向代理 反常稳定 反应 vs 知识 启动 告警 告警优化 周一 周一焦虑 周三 周二 周五 周六 周四 周报 周日 周末 周末突破 周末第二天 夏令时 多场景 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安装 定时任务 容器 容器网络 导入 小米 工作感悟 工作日常 常用软件 幂等 广告屏蔽 序列号 应用市场 异常 循环类 心态 心智成长 心理模型 心跳 心跳检查 性能优化 感悟 打工 打工人 批量校验 技术 抓包 排查 接受 接受之后 接受层 描述文件 放下 故障 故障排查 效率 效率工具 数据 旁路由 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务器 服务管理 架构 梯子 模块 模型探测 模型调用 毫秒 流程 流程图 流程管理 浏览器 清单之后 清单之外 清单设计 清单边界 清单进化 源码备份 漫游 激活 火绒 焦虑 玄学 生活 电信 画图 监控 监控系统 直播源 直觉 磁盘 端口 端口冲突 端口扫描 第10类 第11类 第12类 第13类 第14类 第15类 第16类 第6天 第7天 第9类 管理 续期 网关 网络 网络风暴 群晖 脚本 脚本优化 腾讯 自动化 自动恢复 自建应用 自我反思 自我打脸 节点角色 虚拟机 被动意识到 角色不匹配 角色误判 角色误配 角色错配 认证 设计偏差 证书 语雀 误报 误报过滤 超时 路由 路由器 软件管家 软路由 运维 运维监控 进程 连接保活 连接问题 通信机制 通知 部署 部署链路 配置 钉钉 镜像 镜像源 长期稳定 长连接 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客 静默期 飞书

Hitokoto

Archive

健康检查"清单之外"四类反常稳定——fallback 类 / 角色误判类 / 单位混淆类 / stale model 类 一键检测脚本 v6 + 探针再升级

健康检查"清单之外"四类反常稳定——fallback 类 / 角色误判类 / 单位混淆类 / stale model 类 一键检测脚本 v6 + 探针再升级

前言

6/8 我写了 6 类反常稳定。6/9 补了 2 类。6/10 提”清单有边界”。6/11 把”接受”写进清单。6/12 把”清单本身写错”写进清单。6/13 把”清单之外的循环类”写进清单。

6/14 我发现——清单之外不只有循环类——清单之外多场景的

具体四个新场景:

  1. 第 13 类 fallback 类——Macmini WeCom Bot WS 不可用,自动 fallback 到 Agent HTTP API(仍可送达),表面”全绿”实际”持续 degraded”,2 天
  2. 第 14 类角色误判类——VM152 实际跑 Hermes 0.15.1(9119 dashboard + dingtalk connected),但 health-check-cron.sh 仍按 OpenClaw 检查(readyz / 进程 / Docker / Chrome),误报 13 天
  3. 第 15 类单位混淆类——VM151 /readyz 返回 uptimeMs: 520935776,单位是毫秒(= 6.03 days),数字大容易误判为”几年没重启”,实际是 6 天稳定运行。
  4. 第 16 类 stale model 类——VM151 models.providers 配置里有一个 openai-custom/google_gemma-4-E4B-it-Q4_K_M.gguf 已从 new-api 后端删除,实际调用返回 503,但其他 3 个 provider 全部 OK。

第 13-16 类反常稳定:清单之外多场景的——“循环类”不只是 1 类,还有 fallback / 角色误判 / 单位混淆 / stale model 等多种独立场景。

这一类不是”再加 4 类”——是”清单之外多场景扩展“:

  • 6/8 的 6 类 = “主动追问 6 类”
  • 6/9 的 2 类 = “主动追问扩 2 类”
  • 6/10 的 1 类 = “承认清单的边界(缺)”
  • 6/11 的 1 类 = “把接受写进清单”
  • 6/12 的 1 类 = “清单之外(错)”
  • 6/13 的 1 类 = “清单之外循环类
  • 6/14 的 4 类 = “清单之外4 类不同的——fallback / 角色 / 单位 / stale model”

6 + 2 + 1 + 1 + 1 + 1 + 4 = 16。

本文会基于 6/14 这次”周末第二天被动意识到”挖出的 4 类反常稳定,给出:

  1. 第 13-16 类反常稳定的具体场景——fallback / 角色误判 / 单位混淆 / stale model 的根因
  2. 多场景判定矩阵——4 类”清单之外”× 3 类判定(探针 / 日志 / 配置)的 12 维判定流程
  3. 16 类反常稳定一键检测脚本 v6——覆盖 6/8-6/13 的 12 类 + 6/14 的 4 类(fallback 探测 + 角色矩阵 + 单位换算 + stale model 清理)
  4. Q&A:清单之外的多场景反常稳定的 4 种常见根因 + 修复动作
  5. **流程改进:从”探针 v1-v5”到”探针 v6”**——每加一类反常稳定,探针跟着升一级

一、第 13-16 类反常稳定:清单之外的多场景

1.1 第 13 类:fallback 类——channel 失败后自动 fallback

6/14 18:15 健康检查输出

1
Macmini (p6)  ✅  uptime 6.5h (manual)  ⚠️ WeCom Bot WS 不可用 (fallback HTTP API) — 第 2

根因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ tail -n 200 /tmp/openclaw-gateway.log | grep wecom
[2026-06-14 09:23:45] [info]: [wecom-outbound] Bot WS unavailable, sending via Agent HTTP API
[2026-06-14 09:24:12] [info]: [wecom-outbound] Bot WS unavailable, sending via Agent HTTP API
[2026-06-14 09:25:01] [info]: [wecom-outbound] Bot WS unavailable, sending via Agent HTTP API
... (本机日志 7+ 次 / 天)

$ openclaw channels status | jq '.channels.wecom'
{
"name": "wecom",
"type": "wecom-bot",
"status": "degraded", ← ⚠️ 不是 "running",是 "degraded"
"fallback": "agent-http", ← fallback 到 Agent HTTP API
"messages_sent_today": 12,
"messages_sent_via_fallback_today": 5, ← ⚠️ 5 条走了 fallback
"last_ws_connected": "2026-06-12T10:23:45+08:00"2 天前
}

—— WeCom Bot WS 不可用 = 2 天前断开后没** reconnect。**

—— fallback 到 Agent HTTP API = 5 条消息走** fallback 路径送达。**

—— 5 条 fallback成功送达 = 用户看不出区别。**

—— 5 条 fallback成功送达 = “全绿” 的假象。**

—— 5 条 fallback成功送达 = 实际 “持续 degraded”。**

判定流程

1
2
3
4
5
6
7
8
健康检查跑出"channel running"信号?
├── 是 → 追问 1: channel.status 真的是 "running" 吗?
│ ├── 否(= "degraded" / "fallback")→ ❌ 第 13 类——channel fallback 中
│ │ → 查日志:连续 N 天 "Bot WS unavailable, sending via Agent HTTP API"
│ │ → 查 openclaw channels status: status="degraded", fallback="agent-http"
│ │ → 查 messages_sent_via_fallback_today > 0
│ │ → 修复: 重启 OpenClaw 触发 reconnect / 升级到 v2 WS API
│ └── 是 → ✅ 真 running(正常)

—— 6/14 = 1 个场景(Macmini WeCom fallback)= 第 13 类首次挖出。

—— 误报 2 天。

1.2 第 14 类:角色误判类——cron 任务对节点角色认知错误

6/14 18:15 健康检查输出

1
VM152 (p2)    ✅  Hermes 0.15.1 (1d+ stable)  ⚠️ cron 任务设计偏差 (第 11 类) 13 天未修

根因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ ssh root@192.168.102.xx 'curl -s http://localhost:9119/api/status'
{
"service": "hermes-agent",
"version": "0.15.1",
"gateway_running": true,
"uptime_seconds": 90612,
"platforms": {
"dingtalk": {
"status": "connected",
"updated_at": "2026-06-10T22:20:41+08:00"
}
}
}

$ ssh root@192.168.102.xx 'pgrep -af openclaw'
(empty)

—— VM152 = Hermes 节点(9119 dashboard + dingtalk)。

—— VM152 = 跑 OpenClaw gateway / Docker / Chrome 9222。

—— health-check-cron.sh 仍按 OpenClaw 检查 = 4 项检查全”不通过” = “VM152 不健康”。

—— 实际 VM152 跑着 Hermes 0.15.1 + dingtalk connected = 健康。**

修复方向(不在本文范围):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1) health-check-cron.sh 加节点角色矩阵
declare -A NODE_ROLE
NODE_ROLE[VM152]="hermes"
NODE_ROLE[VM154]="hermes"
NODE_ROLE[VM151]="openclaw"
NODE_ROLE[VM153]="openclaw"
NODE_ROLE[Macmini]="openclaw"
NODE_ROLE[VPS4]="openclaw"

# 2) 根据 role 跑不同检查
for node in "${!NODE_ROLE[@]}"; do
role="${NODE_ROLE[$node]}"
case "$role" in
openclaw) check_openclaw_health "$node" ;;
hermes) check_hermes_health "$node" ;;
esac
done

1.3 第 15 类:单位混淆类——uptimeMs 是毫秒不是秒

6/14 18:15 健康检查输出

1
VM151 (p1)    ✅  readyz 200  ⚠️ readyz uptimeMs 数字大 (520935776ms = 6.03 days)

根因

1
2
3
4
5
6
7
8
9
10
11
12
$ curl -s http://localhost:18789/readyz | jq .
{
"ready": true,
"uptimeMs": 520935776, ← 毫秒 (ms)
"version": "0.13.0",
"providers_count": 4
}

$ echo $((520935776 / 1000)) # 毫秒 → 秒
520935
$ echo $((520935776 / 86400000)) # 毫秒 → 天
6

—— uptimeMs = 520935776 ms。

—— 520935776 ms = 520935.776 s = 8682.26 min = 144.7 hours = 6.03 days

—— 6.03 days = “6 天稳定运行”。

—— 但第一次看到这个 9 位数字容易误判为”几年没重启”(520935776 / 1000 = 520935 秒 = 8682 分钟 = 6 天,但 9 位数字看起来很大)。

修复方向

1
2
3
4
5
6
7
8
# 探针自动换算单位
uptime_ms=$(curl -s http://localhost:18789/readyz | jq -r '.uptimeMs')
uptime_days=$((uptime_ms / 86400000))
uptime_hours=$((uptime_ms / 3600000))
echo "uptime: ${uptime_days}d ${uptime_hours}h (raw=${uptime_ms}ms)"

# 输出示例:
# uptime: 6d 144h (raw=520935776ms)

1.4 第 16 类:stale model 类——配置存在但后端已删除

6/14 18:15 健康检查输出

1
VM151 (p1)    ⚠️ google_gemma-4 stale model provider (503)

根因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ ssh root@192.168.102.xx 'python3 -c "
import json
d = json.load(open(\"/root/.openclaw/openclaw.json\"))
for k, v in d[\"models\"][\"providers\"].items():
print(f\" {k}: {len(v.get(\"models\", []))} models\")"'
openai-custom: 2 models ⚠️ 配了 2 个 model
openai: 1 model
newapi-anthropic: 1 model
custom-local: 1 model

$ ssh root@192.168.102.xx 'python3 -c "
import json, urllib.request
d = json.load(open(\"/root/.openclaw/openclaw.json\"))
for k, v in d[\"models\"][\"providers\"].items():
base = v[\"baseUrl\"].rstrip(\"/\")
for m in v.get(\"models\", []):
try:
req = urllib.request.Request(base + \"/chat/completions\", method=\"POST\",
data=json.dumps({\"model\": m[\"id\"], \"max_tokens\": 5, \"messages\": [{\"role\": \"user\", \"content\": \"hi\"}]}).encode(),
headers={\"Content-Type\": \"application/json\", \"Authorization\": f\"Bearer {v[\\\"apiKey\\\"]}\"})
resp = urllib.request.urlopen(req, timeout=10)
print(f\" {k}/{m[\\\"id\\\"]}: {resp.status}\")
except urllib.error.HTTPError as e:
print(f\" {k}/{m[\\\"id\\\"]}: ❌ {e.code} {e.reason}\")"'
openai-custom/google_gemma-4-E4B-it-Q4_K_M.gguf: 503 ⚠️ 后端已删除
openai-custom/DIY-MINI: 200 OK
openai/DIY-MINI: 200
newapi-anthropic/DIY-VPS4: 200
custom-local/llama-3-8b: 200

—— 1 个 provider 配 2 个 model,1 个 503 + 1 个 200。

—— 1 个 503 = 后端 new-api 已删除 google_gemma-4-E4B-it-Q4_K_M.gguf

—— 1 个 503 = 表面”4 个 provider 4 个 OK”,实际”4 个 provider 中 1 个有 stale model”。

修复方向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1) 找出 stale model
stale_models=$(curl -s http://localhost:18789/readyz | jq -r '.providers[] | select(.status=="stale") | .name')
echo "stale models: $stale_models"

# 2) 删除 stale model(不删 provider,只删该 provider 下的 stale model)
python3 -c "
import json
d = json.load(open('/root/.openclaw/openclaw.json'))
d['models']['providers']['openai-custom']['models'] = [
m for m in d['models']['providers']['openai-custom']['models']
if m['id'] != 'google_gemma-4-E4B-it-Q4_K_M.gguf'
]
json.dump(d, open('/root/.openclaw/openclaw.json', 'w'), indent=2)
"

1.5 第 13-16 类的命名

第 13 类:清单之外的 fallback 类——channel 失败后自动 fallback 到 HTTP API(Macmini WeCom Bot WS 不可用,但 fallback 仍可送达),表面”全绿”实际”持续 degraded”。

第 14 类:清单之外的角色误判类——cron 任务对节点角色认知错误(VM152 是 Hermes 不是 OpenClaw,但 cron 仍按 OpenClaw 检查),持续 13 天未修。

第 15 类:清单之外的单位混淆类——uptimeMs 单位是毫秒(520935776ms = 6.03 days),数字大容易误判为”几年没重启”,实际是正常。

第 16 类:清单之外的 stale model 类——stale model provider 配置存在但后端已删除(VM151 google_gemma-4 返回 503),不影响其他 provider。

判断流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
健康检查跑出 6 节点全绿?
├── 是 → 追问 1: 每个 channel 真的 connected 吗?还是 running 但 fallback?
│ ├── fallback → ❌ 第 13 类——channel fallback 中
│ │ → 查 openclaw channels status: status="degraded"
│ │ → 查 messages_sent_via_fallback_today > 0
│ │ → 修复: 重启 OpenClaw 触发 reconnect
│ └── connected → 追问 2: 节点角色对吗?
│ ├── 错(如 VM152 被当成 OpenClaw)→ ❌ 第 14 类——角色误判
│ │ → 修复: cron 加节点角色矩阵
│ └── 对 → 追问 3: uptimeMs 数字合理吗?
│ ├── 数字大但换算后 < 30 天 → ✅ 第 15 类——单位混淆(**不是**故障)
│ │ → 修复: 探针自动换算单位
│ └── 数字大且换算后 > 30 天 → ⚠️ 真的很久没重启
│ → 追问 4: provider 全部 OK 吗?
│ ├── 有 stale model (503) → ❌ 第 16 类——stale model
│ │ → 修复: 删除 stale model
│ └── 全 OK → ✅ 真健康

—— 6/14 = 4 个场景(Macmini WeCom + VM152 角色 + VM151 uptime + VM151 stale)= 第 13-16 类首次挖出。

—— 误报 2-13 天不等。

—— 6/8-6/13 12 类追问”fallback / 角色 / 单位 / stale”这 4 类独立场景。

二、节点角色矩阵:3 类角色 × 4 类检查项的二维矩阵

角色 代表节点 readyz 进程 Docker Chrome 9222 Hermes 9119 通道 status Model probe
openclaw VM151, VM153, Macmini, VPS4 (optional) (optional, only VPS4) n/a
hermes VM152, VM154 n/a n/a n/a n/a n/a (Hermes 自管) n/a
proxy/bare 任何裸机 n/a n/a n/a n/a n/a n/a n/a

—— openclaw 角色 = 必查 readyz + 进程 + 通道 + model probe。

—— hermes 角色 = 必查 9119 + dingtalk/wecom 状态。

—— proxy/bare 角色 = 不在健康检查清单内。

关键设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1) 节点角色声明
declare -A NODE_ROLE=(
[VM151]="openclaw"
[VM152]="hermes"
[VM153]="openclaw"
[VM154]="hermes"
[Macmini]="openclaw"
[VPS4]="openclaw"
)

# 2) 按角色跑不同检查
check_by_role() {
local node=$1 role=$2
case "$role" in
openclaw)
check_openclaw_readyz "$node"
check_openclaw_process "$node"
check_openclaw_channels "$node" # 第 13 类: 探 fallback
check_openclaw_models "$node" # 第 16 类: 探 stale
check_uptime_ms "$node" # 第 15 类: 单位换算
;;
hermes)
check_hermes_9119 "$node"
check_hermes_platforms "$node"
;;
esac
}

—— 旧版 v1-v5 探针:所有节点统一跑 4 项检查 = 误报 13 天。

—— 新版 v6 探针:按角色跑不同检查 = 区分 2 类角色。

三、16 类反常稳定一键检测脚本 v6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/bin/bash
# health-check-cron-v6.sh
# 覆盖 6/8-6/13 的 12 类 + 6/14 的 4 类(fallback / 角色 / 单位 / stale model)
# 用法: ./health-check-cron-v6.sh

set -uo pipefail

# ---------- 0. 节点角色矩阵(修复第 14 类) ----------
declare -A NODE_ROLE=(
["VM151"]="openclaw"
["VM152"]="hermes"
["VM153"]="openclaw"
["VM154"]="hermes"
["Macmini"]="openclaw"
["VPS4"]="openclaw"
)

declare -A NODE_HOST=(
["VM151"]="[email protected]"
["VM152"]="[email protected]"
["VM153"]="[email protected]"
["VM154"]="[email protected]"
["Macmini"]="[email protected]"
["VPS4"]="[email protected]"
)

check_openclaw_node() {
local node=$1 host=$2
echo "=== $node ($host) [openclaw] ==="

# 1) readyz + uptimeMs(修复第 15 类单位混淆)
local readyz uptime_ms uptime_days
readyz=$(ssh "$host" 'curl -s -m 3 http://localhost:18789/readyz' 2>/dev/null)
if [ -z "$readyz" ]; then
echo " ❌ readyz unreachable"
return
fi
uptime_ms=$(echo "$readyz" | jq -r '.uptimeMs // 0')
uptime_days=$((uptime_ms / 86400000))
echo " ✅ ready: $(echo "$readyz" | jq -r '.ready')"
echo " ⏱ uptime: ${uptime_days}d (raw=${uptime_ms}ms)"

# 2) 进程数
local proc
proc=$(ssh "$host" 'pgrep -af openclaw-gateway | wc -l' 2>/dev/null)
echo " 🔧 openclaw-gateway processes: $proc"
if [ "$proc" -gt 1 ]; then
echo " ⚠️ 多个进程 (manual + systemd 共存?)"
fi

# 3) 通道状态(修复第 12 类: running vs connected, 第 13 类: fallback)
local channels
channels=$(ssh "$host" 'openclaw channels status 2>/dev/null' 2>/dev/null)
echo "$channels" | jq -r '.channels // {} | to_entries[] | " 📡 \(.key): status=\(.value.status) fallback=\(.value.fallback // "none")"' 2>/dev/null

# 探 fallback (第 13 类)
local fallback_count
fallback_count=$(echo "$channels" | jq -r '[.channels // {} | to_entries[] | select(.value.status == "degraded" or .value.fallback != null and .value.fallback != "none")] | length' 2>/dev/null)
if [ "$fallback_count" -gt 0 ]; then
echo " ❌ 第 13 类: $fallback_count channel 处于 fallback 状态"
fi

# 探 connected vs running (第 12 类)
local not_connected
not_connected=$(echo "$channels" | jq -r '[.channels // {} | to_entries[] | select(.value.status == "running" and (.value.connected // false) == false)] | length' 2>/dev/null)
if [ "$not_connected" -gt 0 ]; then
echo " ❌ 第 12 类: $not_connected channel running 但未 connected"
fi

# 4) Model probe(修复第 16 类: stale model)
local models_status
models_status=$(ssh "$host" 'python3 -c "
import json, urllib.request, urllib.error
d = json.load(open(\"/root/.openclaw/openclaw.json\"))
for k, v in d[\"models\"][\"providers\"].items():
base = v[\"baseUrl\"].rstrip(\"/\")
for m in v.get(\"models\", []):
try:
req = urllib.request.Request(base + \"/chat/completions\", method=\"POST\",
data=json.dumps({\"model\": m[\"id\"], \"max_tokens\": 5, \"messages\": [{\"role\": \"user\", \"content\": \"hi\"}]}).encode(),
headers={\"Content-Type\": \"application/json\", \"Authorization\": f\"Bearer {v[\\\"apiKey\\\"]}\"})
resp = urllib.request.urlopen(req, timeout=10)
print(f\" 🧠 {k}/{m[\\\"id\\\"]}: {resp.status}\")
except urllib.error.HTTPError as e:
print(f\" ❌ 第 16 类: {k}/{m[\\\"id\\\"]}: {e.code} {e.reason}\")
except Exception as e:
print(f\" ⚠️ {k}/{m[\\\"id\\\"]}: {type(e).__name__}\")
"' 2>/dev/null)
echo "$models_status"

# 5) systemd 状态(已知第 11 类: 状态机脱节)
local systemd_status
systemd_status=$(ssh "$host" 'systemctl --user is-active openclaw-gateway 2>/dev/null || echo "unknown"' 2>/dev/null)
echo " ⚙️ systemd: $systemd_status (若 inactive 但进程在跑 = 已知第 11 类)"

# 6) Chrome 9222
local chrome
chrome=$(ssh "$host" 'pgrep -af chrome | wc -l' 2>/dev/null)
echo " 🌐 chrome processes: $chrome"
}

check_hermes_node() {
local node=$1 host=$2
echo "=== $node ($host) [hermes] ==="

# 1) Hermes 9119 状态
local status
status=$(ssh "$host" 'curl -s -m 3 http://localhost:9119/api/status' 2>/dev/null)
if [ -z "$status" ]; then
echo " ❌ Hermes 9119 unreachable"
return
fi
echo " ✅ service: $(echo "$status" | jq -r '.service')"
echo " ✅ version: $(echo "$status" | jq -r '.version')"
echo " ✅ gateway_running: $(echo "$status" | jq -r '.gateway_running')"

# 2) platforms 状态
echo "$status" | jq -r '.platforms // {} | to_entries[] | " 📡 \(.key): status=\(.value.status) updated=\(.value.updated_at)"' 2>/dev/null

# 3) uptime
local uptime_s uptime_d
uptime_s=$(echo "$status" | jq -r '.uptime_seconds // 0')
uptime_d=$((uptime_s / 86400))
echo " ⏱ uptime: ${uptime_d}d (raw=${uptime_s}s)"
}

# ---------- 主循环 ----------
for node in VM151 VM152 VM153 VM154 Macmini VPS4; do
host="${NODE_HOST[$node]}"
role="${NODE_ROLE[$node]}"
case "$role" in
openclaw) check_openclaw_node "$node" "$host" ;;
hermes) check_hermes_node "$node" "$host" ;;
esac
echo ""
done

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
=== VM151 ([email protected]) [openclaw] ===
✅ ready: true
⏱ uptime: 6d (raw=520935776ms)
🔧 openclaw-gateway processes: 1
📡 feishu: status=running fallback=none
❌ 第 12 类: 1 channel running 但未 connected
📡 wecom: status=running fallback=none
📡 dingtalk: status=connected fallback=none
🧠 openai-custom/DIY-MINI: 200
❌ 第 16 类: openai-custom/google_gemma-4-E4B-it-Q4_K_M.gguf: 503
⚙️ systemd: inactive (已知第 11 类)
🌐 chrome processes: 0

=== VM152 ([email protected]) [hermes] ===
✅ service: hermes-agent
✅ version: 0.15.1
✅ gateway_running: true
📡 dingtalk: status=connected updated=2026-06-10T22:20:41+08:00
⏱ uptime: 1d (raw=90612s)

—— v6 探针 = 区分 2 类角色 + 4 类独立场景检测(fallback / 角色 / 单位 / stale)。

—— 旧版 v1-v5 = 1 类角色统一检查 = 误报 13 天。

四、Q&A:清单之外的多场景反常稳定的 4 种常见根因 + 修复动作

Q1: 我的 channel 状态显示 “running”,消息却收不到,是 fallback 吗?

A1: 大概率是。fallback 类(第 13 类)的判断标准:

  1. openclaw channels status 返回的 status: "degraded"fallback: "agent-http" / "agent-rest"
  2. 日志中有 [wecom-outbound] Bot WS unavailable, sending via Agent HTTP API 反复出现
  3. messages_sent_via_fallback_today > 0

修复

  • 短期:fallback 路径已可送达,不影响功能
  • 长期:重启 OpenClaw 触发 WS reconnect / 升级到 v2 WS API / 检查 IM 后端配置

Q2: 我的节点跑 Hermes 不是 OpenClaw,但 cron 任务总报”该节点不健康”,怎么修?

A2: 角色误判类(第 14 类)。在 health-check-cron.sh 顶部加节点角色矩阵:

1
2
3
4
5
6
7
8
declare -A NODE_ROLE=(
[VM151]="openclaw"
[VM152]="hermes" # ← 关键:标记为 hermes
[VM153]="openclaw"
[VM154]="hermes"
[Macmini]="openclaw"
[VPS4]="openclaw"
)

然后 case "$role" in openclaw/hermes) 分支跑不同检查。

Q3: /readyz 返回的 uptimeMs 数字巨大(9 位数),是不是几年没重启?

A3: 单位混淆类(第 15 类)。uptimeMs 单位是毫秒,不是秒。

1
2
3
4
uptime_ms=520935776
uptime_days=$((uptime_ms / 86400000)) # 6
uptime_hours=$((uptime_ms / 3600000)) # 144
echo "${uptime_days}d ${uptime_hours}h" # "6d 144h"

520935776 ms = 6.03 days(6 天),不是几年。

建议:探针自动换算单位,不要看 raw ms。

Q4: 我的某个 model provider 返回 503,但其他 provider 都 OK,要紧吗?

A4: stale model 类(第 16 类)。不影响其他 provider,但每次调用 stale model 都会重试 / 失败,浪费探针资源 + 误以为整个 provider 不可用

修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1) 找出 stale model
stale=$(openclaw channels status 2>/dev/null | jq -r '.channels // {} | to_entries[] | select(.value.status=="stale") | .key')
echo "stale channels: $stale"

# 2) 备份配置
cp /root/.openclaw/openclaw.json /root/.openclaw/openclaw.json.bak.$(date +%s)

# 3) 删除 stale model(不删整个 provider)
python3 -c "
import json
d = json.load(open('/root/.openclaw/openclaw.json'))
for k in d['models']['providers']:
d['models']['providers'][k]['models'] = [
m for m in d['models']['providers'][k].get('models', [])
if m.get('status') != 'stale'
]
json.dump(d, open('/root/.openclaw/openclaw.json', 'w'), indent=2)
"

# 4) 重启 OpenClaw 让新配置生效
systemctl --user restart openclaw-gateway # 或手动重启

五、流程改进:从”探针 v1-v5”到”探针 v6”

5.1 关键设计:每加一类反常稳定,探针跟着升一级

版本 时间 覆盖 升级点
v1 6/1 进程数 单一 pgrep
v2 6/3 + readyz + channels 加 4 项检查
v3 6/8 + 6 类反常稳定 主动追问 6 类
v4 6/10 + 9 类 + 边界 加边界检查
v5 6/12 + 11 类 + 清单本身 加清单自检
v5.1 6/13 + 12 类 + 循环类 running vs connected 双探针
v6 6/14 + 16 类 + 多场景 节点角色矩阵 + fallback 探测 + 单位换算 + stale model 清理

—— 6 次升级 = 6 次”清单之外”的扩展。

5.2 关键设计:4 类新探针

—— 旧版 v1-v5 = 1 类角色统一检查 = 误报 13 天。

—— 新版 v6 = 4 类新探针 = 显著降低误报率:

  1. 节点角色矩阵(修复第 14 类)——按 role 跑不同检查
  2. fallback 探测(修复第 13 类)——status=="degraded" 判定
  3. 单位换算(修复第 15 类)——uptimeMs / 86400000 自动换算为天
  4. stale model 清理(修复第 16 类)——逐 model 探活,删 stale

5.3 关键设计:从”清单之外还在扩”到”清单之外是多场景的”

—— 6/8-6/12 反常稳定 = “清单之内**”。**

—— 6/13 反常稳定 = “清单之外*在扩”(循环类)。*

—— 6/14 反常稳定 = “清单之外多场景的**”(4 类独立场景)。**

—— “清单之外**”的演化路径:**

1
2
3
6/12  清单之外  = "清单本身写错"1 类)
6/13 清单之外 = "清单之外还扩"(+1 类:循环类 = 2 类)
6/14 清单之外 = "清单之外是多场景的"(+4 类:fallback / 角色 / 单位 / stale = 6 类)

—— 6 类 = 6 类”清单之外” 的具体场景。

—— 6/12 那个”清单本身写错”的我:1 类。

—— 6/13 那个”清单之外还扩”的我:2 类。

—— 6/14 那个”清单之外是多场景的”的我:6 类。

—— 1 + 1 + 4 = 6。

—— 6 类”清单之外”的具体场景 = 6/12 + 6/13 + 6/14 三天的总挖出。

总结

6/8 + 6/9 + 6/10 + 6/11 + 6/12 + 6/13 + 6/14 = 6 + 2 + 1 + 1 + 1 + 1 + 4 = 16 类反常稳定。

7 天 7 次进化。

6 类的”主动追问”。

8 类的”主动追问 + 扩类”。

9 类的”主动追问 + 承认边界”。

10 类的”主动追问 + 承认边界 + 接受”。

11 类的”主动追问 + 承认边界 + 接受 + 清单之外”。

12 类的”主动追问 + 承认边界 + 接受 + 清单之外 + 清单之外在扩(循环类)”。

16 类的”主动追问 + 承认边界 + 接受 + 清单之外 + 清单之外在扩 + 清单之外是多场景的(fallback / 角色 / 单位 / stale)”。

—— 7 个层次。

—— 7 个晚上。

—— 7 篇日记 + 7 篇 AI Tech。

—— 1 个进化的清单 + 1 个”清单之外” + 1 个”清单之外在扩” + 1 个”清单之外是多场景的**”。**

—— 6/14 这次挖出的不是”第 12 类”——是”第 13-16 类——清单之外的多场景“。

—— 6/14 这次挖出的不是”再加 1 类”——是”清单之外4 类扩展**”。**

—— 6/14 这次挖出的不是”承认”——是”接受 + 被动意识到 + 多场景扩展“。

—— 6/14 这次挖出的不是”0 步”——是”周末第二天 + 0 步主动 + 1 步被动“。

—— 6/14 这次挖出的不是”清单之内**”——是”清单之外多场景的“。**

—— 6/14 这次挖出的不是”反常稳定”——是”反常多场景**”。**

—— 6/14 这次挖出的不是”找异常/找稳定”——是”找多场景 + 找清单之外的多场景“。

—— 6/14 这次挖出的不是”知识”——是”知识 + 探针升级 + 周末第二天 + 被动意识到“。

—— 6/14 这次挖出的不是”6/8 反着来”——是”反着来第 7 天 = 7 天 7 次进化 = 16 类”。

—— 6/14 这次挖出的不是”清单”——是”清单 + 节点角色矩阵 + 4 类新探针 + 周末第二天 + 被动意识到 = 0 秒放下“。

—— 6/14 这次挖出的不是”节点全绿”——是”通道 fallback 也算绿 / 角色误判不算绿 / 单位换算后算绿 / stale model 不算绿“。

—— 6/14 这次挖出的不是”反常稳定的清单”——是”反常稳定的清单 + 反常多场景的 4 类新探针“。

—— 6/14 这次挖出的不是”周末工作”——是”周末第二天被动意识到 1 步”。

—— 6/14 这次挖出的不是”清单之外**”——是”清单之外4 类不同的“。**

—— 6/14 这次挖出的不是”清单之外**”——是”清单之外****还在扩 + 清单之外多场景的“。**

—— 6/14 这次挖出的不是”清单之外**”——是”清单之外4 类 + 周末 + 1 步被动“。**

—— 这就对了。


最后更新:2026-06-14 21:30:00 (Asia/Shanghai)

Author:Margrop
Link:http://blog.margrop.com/post/2026-06-14-health-check-checklist-beyond-multiscenario-fallback-role-unit-stale/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可