Skip to content

调试与排错

插件跑在宿主进程里,没有独立 daemon,所以排错思路和独立 CLI 的调试略有不同:先确认插件装没装对,再确认 Relay 隧道连没连上,最后看日志定位具体环节

第一步:doctor 体检

bash
openclaw ntf doctor
openclaw ntf doctor --json
openclaw ntf doctor --fix

doctor 实际按优先级依次跑 9 个 checker:

级别Checker检查内容可自动修复
criticaldangerous-flagsgateway.controlUi.dangerouslyDisableDeviceAuth 是否为 true是(写回配置并生成 .bak
criticaltrusted-proxygateway.auth.mode 是否为 trusted-proxy(存在明显安全风险)否,需手动确认
criticalcredentialsapi-key 是否已配置否,提示执行 ntf auth set-api-key
warntrusted-proxy-allow-userstrustedProxy.allowUsers 是否为空
warnstate-dir-perms状态目录权限位是否对 group/other 开放是(chmod 700
warntool-policy是否有 agent 仍用默认(宽松)工具策略
warntunnelRelay 隧道当前状态(见下)
infonode-versionNode 版本是否 < 22.12.0
infoplugin-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 隧道

bash
openclaw ntf auth show          # api-key 是否就位
openclaw ntf tunnel-status      # 隧道连接状态

tunnel-status 连上时大致长这样:

json
{ "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

第三步:看日志

bash
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 时发现没有 apiKeyopenclaw 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>查询了一个不存在的录音 IDntf rec list 确认 ID
非法状态转换: <from> → <to>录音状态机被跳跃调用状态机严格线性,不允许跳过中间状态,通常是内部 bug 而非用户可修复问题
light_control supports 1-N segmentslight send --segments 传了超过上限的段数减少段数
repeat_times 必须是 >=0 的整数灯效 repeat_times 参数不合法修正参数
日志目录不可用 (LOGS_UNAVAILABLE)ntf log 时日志目录从未创建过说明插件从未成功运行过,或 stateDir 配置错误
workspaceDir and stateDir both unavailablemonitor / 灯效规则等功能找不到任何可写目录检查两个目录路径与权限
[whisper-local] 未找到 whisper.cpp 二进制文件。请安装 whisper.cpp 并确保 whisper-cli 在 PATH 中...asr.mode=local 但本机没装 whisper.cppbrew install whisper-cpp(或对应平台方式),确保 whisper-cli 在 PATH
OGG/Opus 格式需要 ffmpeg 或 opus-tools(brew install opus-tools)本地 ASR 转码缺依赖安装 ffmpegopus-tools
请安装 ffmpeg(brew install ffmpeg)或确保音频文件为 WAV 格式同上,非 Opus 格式缺 ffmpeg安装 ffmpeg

常见症状 → 处理

装完插件找不到 ntf 命令

先确认插件确实注册成功:

bash
openclaw plugins list

如果列表里没有 phone-notifications,回到安装重新走一遍;如果已有其它插件占用了 ntf 别名,用 openclaw phone-notifications --help 代替。一键脚本安装时还会尝试 npm link 暴露裸 ntf 命令,这一步失败(npm 缺失、link 失败、或链接出来的命令不在 PATH 上)不会导致安装失败,只会在安装总结里给出警告,不影响插件本身在宿主内可用。

QClaw 下命令不生效

QClaw 通常没有全局 openclaw 命令,装完插件需要重启 QClaw 桌面应用才会生效,然后用 QClaw 自带的 wrapper 执行等价子命令。

手机端推送不进来

按顺序确认:

  1. openclaw ntf auth show —— api-key 已配置;
  2. openclaw ntf tunnel-status —— connected: true
  3. 宿主没有同时开着 Tailscale Funnel / 外部 remote gateway(两者和 Relay 隧道互斥,见上文);
  4. openclaw ntf log --keyword ingest —— 确认请求确实到达了插件的 ingest 逻辑,而不是卡在宿主 gateway 鉴权那一层;
  5. 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"}
  6. 如果确认请求已到达但通知「消失」了,检查 ignoredApps 配置有没有把对应 app 包名过滤掉——被过滤和被去重(dedupedById/dedupedByContent)都不会报错,只是不会落盘,日志里能看到对应计数。

灯效规则没触发

灯效规则是先落盘、再异步评估(debounce 1s),不是收到通知立刻判断。先确认通知确实落盘了(openclaw ntf search --limit 1),再看日志里有没有对应的规则评估记录;如果规则本身被禁用,评估会跳过。

录音卡在某个状态不推进

录音走一条不允许跳跃的状态机,先查当前状态:

bash
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 安装」,需要先修正属主。

下一步

  • 命令参考 —— openclaw ntf 全部子命令。
  • 独立 CLI 调试 —— 两者共用同一套 Relay 实现,很多排错思路可以互相参照。