调试与排错
插件跑在宿主进程里,没有独立 daemon,所以排错思路和独立 CLI 的调试略有不同:先确认插件装没装对,再确认 Relay 隧道连没连上,最后看日志定位具体环节。
第一步:doctor 体检
openclaw ntf doctor
openclaw ntf doctor --json
openclaw ntf doctor --fixdoctor 实际按优先级依次跑 9 个 checker:
| 级别 | Checker | 检查内容 | 可自动修复 |
|---|---|---|---|
| critical | dangerous-flags | gateway.controlUi.dangerouslyDisableDeviceAuth 是否为 true | 是(写回配置并生成 .bak) |
| critical | trusted-proxy | gateway.auth.mode 是否为 trusted-proxy(存在明显安全风险) | 否,需手动确认 |
| critical | credentials | api-key 是否已配置 | 否,提示执行 ntf auth set-api-key |
| warn | trusted-proxy-allow-users | trustedProxy.allowUsers 是否为空 | 否 |
| warn | state-dir-perms | 状态目录权限位是否对 group/other 开放 | 是(chmod 700) |
| warn | tool-policy | 是否有 agent 仍用默认(宽松)工具策略 | 否 |
| warn | tunnel | Relay 隧道当前状态(见下) | 否 |
| info | node-version | Node 版本是否 < 22.12.0 | 否 |
| info | plugin-version | 是否有新版本可更新 | 否(提示 ntf update) |
注意:
doctor不会检查workspaceDir/stateDir是否可写、不会检查本地 ASR 依赖(whisper-cli/opusdec/ffmpeg),也不会检查是否命中 Tailscale / 外部 remote gateway 的互斥降级。这三类问题目前需要按下文「常见症状」手动排查,不要指望doctor报出来。
tunnel checker 的四种输出:
| 状态 | 含义 |
|---|---|
STATUS_NOT_FOUND | 隧道状态文件不存在(插件从未连接成功过,或状态目录被清空) |
STATUS_INVALID | 隧道状态文件解析失败(文件损坏) |
STATUS_STALE | 状态文件说「已连接」,但本地锁文件缺失/过期/对应进程已死 |
| 「隧道未连接 (state=...)」 | 携带 lastDisconnectReason,说明最近一次断开的原因 |
第二步:确认 Relay 隧道
openclaw ntf auth show # api-key 是否就位
openclaw ntf tunnel-status # 隧道连接状态tunnel-status 连上时大致长这样:
{ "ok": true, "connected": true, "reconnectAttempt": 0,
"relayUrl": "wss://openclaw-service.yoooclaw.com/..." }| 字段 | 含义 |
|---|---|
connected | 是否已建立 WebSocket 并在心跳 |
reconnectAttempt | 累计重连次数,0 表示一直没断过 |
lastDisconnectReason | 最近一次断开原因 |
如果 connected 一直是 false,先看两件事:api-key 是否配置(openclaw ntf auth show),以及宿主是否配置了 gateway.tailscale.mode 或外部 gateway.remote.url —— 命中这两种情况时,插件会主动在启动阶段跳过 Relay 隧道,日志会打一行 Relay tunnel: detected ... in host gateway config, skipping relay startup to keep ingress mutually exclusive;这是设计如此,不是故障。此外,一旦有外部直连的 POST /notifications 请求打进来(非 Relay 内部转发),插件也会主动断开已经建立的 Relay 隧道,避免双入口并存。
同一台机器上如果有两个插件进程都想连 Relay,靠 relay-tunnel.lock 文件(记录 pid/startedAt)互斥:后启动的进程发现锁文件对应的进程还活着,会直接跳过、不报错,日志里能看到 another local process already owns the tunnel lock。
第三步:看日志
openclaw ntf log --keyword relay
openclaw ntf log --keyword error --limit 100日志同样写到宿主状态目录下的 plugins/phone-notifications/logs/YYYY-MM-DD.log,具体路径可用 openclaw ntf storage-path 查,默认保留 30 天。生产(非 beta)版本会对日志做脱敏:API key/token/密码、消息正文、发件人、转写文本、URL、邮箱、手机号、JWT、长十六进制串都会被替换成 [redacted] / [redacted-secret] / [redacted-url] 之类的占位符。如果你在日志里看不到通知原文,这是预期行为,不是丢数据;需要看未脱敏日志时,先 openclaw ntf update --beta 切到 beta 频道构建。
关键日志行含义可以对照独立 CLI 调试文档里的日志行表,两边共用同一套 RelayClient 实现,日志格式基本一致。
常见错误信息对照
以下是插件代码里会抛出/返回的原始错误信息,遇到时可以直接搜索关键字定位:
| 错误信息 | 触发场景 | 处理 |
|---|---|---|
API Key 未设置,请先写入 <credentialsPath>,或通过宿主 CLI 执行 ntf auth set-api-key <apiKey> | 读取 credentials 时发现没有 apiKey | openclaw ntf auth set-api-key <key> |
apiKey 未设置,请先执行 ntf auth set-api-key <apiKey>(或 <credentialsPath>) | Relay 连接建立时鉴权失败 | 同上;jvsclaw 场景下会自动指数退避重试 apiKey 换取,直到成功或插件停止 |
未在 <configPath> 找到 link_secret(路径: plugins.entries.phone-notifications.config.link_secret) | JvsClaw 场景,宿主没有注入 link_secret | 检查宿主是否正确下发了该配置,一般不需要手动填写 |
instance/ready HTTP <status> / instance/ready 返回 code=... msg=... | JvsClaw apiKey 换取接口报错 | 检查网络与 JvsClaw 控制面状态 |
通知存储目录不可用: <path> | stateDir 和 workspaceDir 都没有可写的通知存储目录 | 检查两个目录的写权限;插件优先使用 stateDir,只有它不可写时才回落到 workspaceDir |
录音存储目录不可用: <path> | 同上,录音场景 | 同上 |
录音不存在: <recordingId> | 查询了一个不存在的录音 ID | 用 ntf rec list 确认 ID |
非法状态转换: <from> → <to> | 录音状态机被跳跃调用 | 状态机严格线性,不允许跳过中间状态,通常是内部 bug 而非用户可修复问题 |
light_control supports 1-N segments | light send --segments 传了超过上限的段数 | 减少段数 |
repeat_times 必须是 >=0 的整数 | 灯效 repeat_times 参数不合法 | 修正参数 |
日志目录不可用 (LOGS_UNAVAILABLE) | ntf log 时日志目录从未创建过 | 说明插件从未成功运行过,或 stateDir 配置错误 |
workspaceDir and stateDir both unavailable | monitor / 灯效规则等功能找不到任何可写目录 | 检查两个目录路径与权限 |
[whisper-local] 未找到 whisper.cpp 二进制文件。请安装 whisper.cpp 并确保 whisper-cli 在 PATH 中... | asr.mode=local 但本机没装 whisper.cpp | brew install whisper-cpp(或对应平台方式),确保 whisper-cli 在 PATH |
OGG/Opus 格式需要 ffmpeg 或 opus-tools(brew install opus-tools) | 本地 ASR 转码缺依赖 | 安装 ffmpeg 或 opus-tools |
请安装 ffmpeg(brew install ffmpeg)或确保音频文件为 WAV 格式 | 同上,非 Opus 格式缺 ffmpeg | 安装 ffmpeg |
常见症状 → 处理
装完插件找不到 ntf 命令
先确认插件确实注册成功:
openclaw plugins list如果列表里没有 phone-notifications,回到安装重新走一遍;如果已有其它插件占用了 ntf 别名,用 openclaw phone-notifications --help 代替。一键脚本安装时还会尝试 npm link 暴露裸 ntf 命令,这一步失败(npm 缺失、link 失败、或链接出来的命令不在 PATH 上)不会导致安装失败,只会在安装总结里给出警告,不影响插件本身在宿主内可用。
QClaw 下命令不生效
QClaw 通常没有全局 openclaw 命令,装完插件需要重启 QClaw 桌面应用才会生效,然后用 QClaw 自带的 wrapper 执行等价子命令。
手机端推送不进来
按顺序确认:
openclaw ntf auth show—— api-key 已配置;openclaw ntf tunnel-status——connected: true;- 宿主没有同时开着 Tailscale Funnel / 外部 remote gateway(两者和 Relay 隧道互斥,见上文);
openclaw ntf log --keyword ingest—— 确认请求确实到达了插件的 ingest 逻辑,而不是卡在宿主 gateway 鉴权那一层;- HTTP 备选接入模式下,确认请求方式正确:
GET/其它非POST方法会直接405;请求体不是合法 JSON 会400 {"ok":false,"error":"Invalid JSON"};顶层不是数组会400 {"ok":false,"error":"notifications must be an array"};插件存储服务还没就绪时会503 {"ok":false,"error":"Service Not Ready"}; - 如果确认请求已到达但通知「消失」了,检查
ignoredApps配置有没有把对应 app 包名过滤掉——被过滤和被去重(dedupedById/dedupedByContent)都不会报错,只是不会落盘,日志里能看到对应计数。
灯效规则没触发
灯效规则是先落盘、再异步评估(debounce 1s),不是收到通知立刻判断。先确认通知确实落盘了(openclaw ntf search --limit 1),再看日志里有没有对应的规则评估记录;如果规则本身被禁用,评估会跳过。
录音卡在某个状态不推进
录音走一条不允许跳跃的状态机,先查当前状态:
openclaw ntf rec status <recording-id>停在 sync_failed 通常是网络或宿主 gateway 鉴权问题,会自动退回 syncing_openclaw 重试;停在 transcribe_failed 结合 asr.mode 检查对应依赖(本地模式检查 whisper-cli/ffmpeg/opusdec,api/托管模式检查网络与 api-key),会退回 transcribing 重试。ASR 失败不会污染 synced 主链路,可以原地重试。receiving_failed 只能退回 receiving 重新接收,不会自动跳到后续状态。
卸载插件
目前插件没有独立的 uninstall 命令。需要卸载时,手动删除插件安装目录,并从宿主 openclaw.json 里移除 plugins.entries.phone-notifications 配置项,然后重启 gateway。重新运行安装脚本等同于原地升级(会自动把旧版本备份为 <path>.bak.<timestamp>,只保留最近一份备份)。
安装过程中报权限错误
安装器会给出针对性的提示:
- Windows 上的
EACCES/EPERM:通常是 OpenClaw / QClaw / JvsClaw 仍在运行,文件被进程锁定,先完全退出宿主再重试; - Unix 上的
EACCES/EPERM:可能是之前用sudo装过,或当前用户对目录没有写权限,先关闭宿主,再修正目录属主或删除旧插件目录后重试; - 已安装目录属主是
root且当前用户不是root:会直接中止并提示「可能是之前使用 sudo 安装」,需要先修正属主。