Debugging & Troubleshooting
The plugin runs inside the host process with no standalone daemon, so troubleshooting differs a bit from debugging the standalone CLI: first confirm the plugin is installed correctly, then confirm the Relay tunnel is connected, and finally check logs to pin down the specific step.
Step 1: run doctor
openclaw ntf doctor
openclaw ntf doctor --json
openclaw ntf doctor --fixdoctor actually runs 9 checkers in priority order:
| Level | Checker | What it checks | Auto-fixable |
|---|---|---|---|
| critical | dangerous-flags | Whether gateway.controlUi.dangerouslyDisableDeviceAuth is true | Yes (writes config back, keeps a .bak) |
| critical | trusted-proxy | Whether gateway.auth.mode is trusted-proxy (a real security risk) | No, needs manual confirmation |
| critical | credentials | Whether the api-key is configured | No, prompts to run ntf auth set-api-key |
| warn | trusted-proxy-allow-users | Whether trustedProxy.allowUsers is empty | No |
| warn | state-dir-perms | Whether the state directory's permission bits are open to group/other | Yes (chmod 700) |
| warn | tool-policy | Whether any agent still uses the default (permissive) tool profile | No |
| warn | tunnel | Current Relay tunnel status (see below) | No |
| info | node-version | Whether Node is older than 22.12.0 | No |
| info | plugin-version | Whether a newer plugin version is available | No (suggests ntf update) |
Note:
doctordoes not check whetherworkspaceDir/stateDiris writable, does not check local ASR dependencies (whisper-cli/opusdec/ffmpeg), and does not check whether the Tailscale / external remote-gateway mutual-exclusion path has kicked in. These three classes of issue currently need to be diagnosed manually per the "common symptoms" section below — don't expectdoctorto surface them.
The tunnel checker reports one of four states:
| State | Meaning |
|---|---|
STATUS_NOT_FOUND | The tunnel status file doesn't exist (the plugin has never connected successfully, or the state dir was cleared) |
STATUS_INVALID | The tunnel status file failed to parse (corrupted) |
STATUS_STALE | The status file claims "connected" but the local lock file is missing/stale/its owning process is dead |
| "隧道未连接 (state=...)" | Carries lastDisconnectReason, explaining the most recent disconnect |
Step 2: confirm the Relay tunnel
openclaw ntf auth show # is the api-key in place
openclaw ntf tunnel-status # tunnel connection statusWhen connected, tunnel-status looks roughly like:
{ "ok": true, "connected": true, "reconnectAttempt": 0,
"relayUrl": "wss://openclaw-service.yoooclaw.com/..." }| Field | Meaning |
|---|---|
connected | Whether the WebSocket is established and heartbeating |
reconnectAttempt | Cumulative reconnect count; 0 means it has never dropped |
lastDisconnectReason | Reason for the most recent disconnect |
If connected stays false, check two things first: whether the api-key is configured (openclaw ntf auth show), and whether the host has gateway.tailscale.mode or an external gateway.remote.url configured — when either is true, the plugin deliberately skips starting the Relay tunnel at startup, logging a line like Relay tunnel: detected ... in host gateway config, skipping relay startup to keep ingress mutually exclusive. That's by design, not a failure. Additionally, the moment an external, directly-addressed POST /notifications request arrives (i.e. not internal Relay-forwarded traffic), the plugin will proactively tear down an already-established Relay tunnel to avoid running two ingress paths at once.
If two plugin processes on the same machine both try to connect to Relay, a relay-tunnel.lock file (recording pid/startedAt) mutually excludes them: whichever process starts second sees the lock owner is still alive and silently skips connecting — no error — logging another local process already owns the tunnel lock.
Step 3: check logs
openclaw ntf log --keyword relay
openclaw ntf log --keyword error --limit 100Logs are written under plugins/phone-notifications/logs/YYYY-MM-DD.log in the host state directory; find the exact path with openclaw ntf storage-path. Logs are retained for 30 days by default. Production (non-beta) builds redact log content: API keys/tokens/passwords, message bodies, sender names, transcripts, URLs, emails, phone numbers, JWTs, and long hex strings are all replaced with placeholders like [redacted] / [redacted-secret] / [redacted-url]. If you can't see notification content in the logs, that's expected behavior, not data loss — to see unredacted logs, switch to the beta channel first with openclaw ntf update --beta.
Key log-line meanings mirror the log-line table in the standalone CLI debugging doc — both share the same RelayClient implementation, so the log format is largely identical.
Common error messages
These are the literal error strings the plugin code throws or returns — searchable keywords to locate the cause quickly:
| Error message | When it happens | What to do |
|---|---|---|
API Key 未设置,请先写入 <credentialsPath>,或通过宿主 CLI 执行 ntf auth set-api-key <apiKey> | Reading credentials finds no apiKey | openclaw ntf auth set-api-key <key> |
apiKey 未设置,请先执行 ntf auth set-api-key <apiKey>(或 <credentialsPath>) | Auth failed while establishing the Relay connection | Same as above; on JvsClaw the apiKey exchange retries indefinitely with exponential backoff until it succeeds or the plugin stops |
未在 <configPath> 找到 link_secret(路径: plugins.entries.phone-notifications.config.link_secret) | JvsClaw case: the host never injected link_secret | Check whether the host correctly delivered this config; it usually isn't set manually |
instance/ready HTTP <status> / instance/ready 返回 code=... msg=... | The JvsClaw apiKey-exchange endpoint errored | Check network connectivity and JvsClaw control-plane status |
通知存储目录不可用: <path> | Neither stateDir nor workspaceDir has a writable notification storage directory | Check write permissions on both; the plugin prefers stateDir and only falls back to workspaceDir if it's unwritable |
录音存储目录不可用: <path> | Same, for recordings | Same as above |
录音不存在: <recordingId> | Queried a recording ID that doesn't exist | Confirm the ID with ntf rec list |
非法状态转换: <from> → <to> | The recording state machine was called out of order | The state machine is strictly linear and never allows skipping states; this is usually an internal bug rather than something a user can fix |
light_control supports 1-N segments | light send --segments exceeded the maximum segment count | Reduce the number of segments |
repeat_times 必须是 >=0 的整数 | An invalid repeat_times value was passed to light control | Fix the parameter |
日志目录不可用 (LOGS_UNAVAILABLE) | ntf log ran but the log directory was never created | The plugin has never run successfully, or stateDir is misconfigured |
workspaceDir and stateDir both unavailable | A feature like monitors or light rules couldn't find any writable directory | Check both directory paths and permissions |
[whisper-local] 未找到 whisper.cpp 二进制文件。请安装 whisper.cpp 并确保 whisper-cli 在 PATH 中... | asr.mode=local but whisper.cpp isn't installed locally | Install it (e.g. brew install whisper-cpp) and make sure whisper-cli is on PATH |
OGG/Opus 格式需要 ffmpeg 或 opus-tools(brew install opus-tools) | Local ASR transcoding is missing a dependency | Install ffmpeg or opus-tools |
请安装 ffmpeg(brew install ffmpeg)或确保音频文件为 WAV 格式 | Same, for non-Opus formats missing ffmpeg | Install ffmpeg |
Common symptoms → fixes
ntf command not found after installing
First confirm the plugin actually registered:
openclaw plugins listIf phone-notifications isn't in the list, go back to Installation and redo it; if another plugin already claims the ntf alias, use openclaw phone-notifications --help instead. The one-click install script also tries npm link to expose a bare ntf command; if that step fails (missing npm, npm link failing, or the linked binary not landing on PATH), it's not a fatal install error — it's only surfaced as a warning in the install summary and doesn't affect the plugin's own availability inside the host.
Commands don't take effect on QClaw
QClaw usually has no global openclaw command. After installing the plugin, restart the QClaw desktop app for it to take effect, then run the equivalent subcommand through QClaw's own wrapper.
Phone-side pushes aren't coming through
Check in order:
openclaw ntf auth show— the api-key is configured;openclaw ntf tunnel-status—connected: true;- the host isn't simultaneously running Tailscale Funnel / an external remote gateway (both are mutually exclusive with the Relay tunnel, see above);
openclaw ntf log --keyword ingest— confirm the request actually reached the plugin's ingest logic, rather than getting stuck at host gateway authentication;- in the HTTP fallback mode, confirm the request itself is well-formed: any non-
POSTmethod fails with405; a non-JSON body fails with400 {"ok":false,"error":"Invalid JSON"}; a non-array top level fails with400 {"ok":false,"error":"notifications must be an array"}; the storage service not being ready yet returns503 {"ok":false,"error":"Service Not Ready"}; - if the request definitely arrived but the notification "disappeared," check whether
ignoredAppsfiltered out that app's package name — both filtering and dedup (dedupedById/dedupedByContent) happen silently without an error, they just don't get persisted; the corresponding counts show up in the logs.
Light rules aren't triggering
Light rules are evaluated after being written to disk, asynchronously (1s debounce) — not synchronously the instant a notification arrives. First confirm the notification actually landed (openclaw ntf search --limit 1), then check the logs for a matching rule-evaluation entry; if the rule itself is disabled, evaluation is skipped.
A recording is stuck in one state
Recordings go through a state machine that doesn't allow skipping states. Check the current state first:
openclaw ntf rec status <recording-id>Stuck at sync_failed is usually a network or host gateway auth issue, and automatically falls back to syncing_openclaw to retry; stuck at transcribe_failed, check the dependency matching your asr.mode (local mode: check whisper-cli/ffmpeg/opusdec; api/hosted mode: check network and api-key), and it falls back to transcribing to retry. An ASR failure doesn't pollute the synced main chain and can be retried in place. receiving_failed can only fall back to receiving to re-receive — it never auto-advances to a later state.
Uninstalling the plugin
There's currently no dedicated uninstall command. To remove it, manually delete the plugin's install directory and remove the plugins.entries.phone-notifications entry from the host's openclaw.json, then restart the gateway. Re-running the install script is equivalent to an in-place upgrade (it automatically backs up the old version as <path>.bak.<timestamp>, keeping only the most recent backup).
Permission errors during installation
The installer gives targeted guidance:
EACCES/EPERMon Windows: usually means OpenClaw / QClaw / JvsClaw is still running and has files locked — fully quit the host first, then retry;EACCES/EPERMon Unix: may mean a previous install usedsudo, or the current user lacks write permission — close the host, then fix directory ownership or delete the old plugin directory and retry;- an existing install directory owned by
rootwhile the current user isn'troot: aborts immediately with a hint that a previous install likely usedsudo; fix the ownership first.
Next steps
- Command Reference — all
openclaw ntfsubcommands. - CLI Debugging — both share the same Relay implementation, so many troubleshooting steps carry over.