zeroclaw-labs/zeroclaw · error · anyhow::Error
Signal RPC error {code}: {msg}
Error message
Signal RPC error {code}: {msg} What it means
Raised by SignalChannel::rpc_request when the signal-cli daemon's JSON-RPC endpoint ({http_url}/api/v1/rpc) answers with an envelope containing an 'error' object. The numeric code defaults to -1 and message to 'unknown' when the daemon omits them. Every outbound Signal operation flows through this — send_poll, send, start_typing, add_reaction, remove_reaction — so this error is the universal surface for signal-cli failures such as unregistered numbers, untrusted identities, or attachment problems.
Source
Thrown at crates/zeroclaw-channels/src/signal.rs:376
// 201 = success with no body (e.g. typing indicators)
if resp.status().as_u16() == 201 {
return Ok(None);
}
let text = resp.text().await?;
if text.is_empty() {
return Ok(None);
}
let parsed: serde_json::Value = serde_json::from_str(&text)?;
if let Some(err) = parsed.get("error") {
let code = err.get("code").and_then(|c| c.as_i64()).unwrap_or(-1);
let msg = err
.get("message")
.and_then(|m| m.as_str())
.unwrap_or("unknown");
anyhow::bail!("Signal RPC error {code}: {msg}");
}
Ok(parsed.get("result").cloned())
}
/// Process a single SSE envelope, returning one or more
/// `ChannelMessage`s. Most envelopes produce 0 or 1 messages; a
/// multi-select poll vote produces N (one per selected option).
///
/// Inbound shape may be plain text (`dataMessage.message`) OR a
/// poll-vote (`dataMessage.pollAnswer` or `dataMessage.pollVote`). For
/// poll-votes we emit a synthetic message per selected option whose `content` is a
/// documented sentinel: `"[choice-index]N"` for real signal-cli
/// `pollVote` payloads, or `"[choice]<selected-title>"` when an
/// alternate payload supplies titles. Consumers
/// can match this prefix to correlate the vote with their original
/// option set, or ignore it if they don't handle poll votes.
///View on GitHub (pinned to 88bb9c8533)
Solutions
- Check signal-cli's own logs — the RPC message mirrors a stack trace there with the real cause
- Verify the recipient number in E.164 format and registered on Signal (and not the bot's own number)
- For untrusted-identity errors, re-verify trust for that number (trust/new-safety-number flow) or clear the broken session
- Confirm the daemon version supports the RPC method being called (typing, polls, reactions differ across versions)
Defensive patterns
Strategy: try-catch
Try / catch
if let Err(err) = channel.send(&recipient, &text).await {
let msg = format!("{err:#}");
if msg.starts_with("Signal RPC error -1") && msg.contains("Unregistered") {
return Ok(mark_unreachable(recipient)); // permanent: number not on Signal, don't retry
}
if msg.starts_with("Signal RPC error") {
tokio::time::sleep(Duration::from_secs(3)).await;
return channel.send(&recipient, &text).await; // transient daemon hiccup: one retry
}
return Err(err);
} Prevention
- Normalize recipient numbers to E.164 and never send to the bot's own number
- Handle identity/trust errors by re-verifying safety numbers instead of resending blindly
- Keep signal-cli daemon version in lockstep with the RPC method names the channel uses
When it happens
Trigger: Any rpc_request(method, params) call where the parsed JSON-RPC response has .error: sending to a number not on Signal ('Unregistered user' style -1 errors), sending to the bot's own number, untrusted/changed safety numbers (identity changes), invalid recipient formats, or daemon-internal failures.
Common situations: Recipient number not E.164 or not registered on Signal; the signal-cli account's trust store missing an identity after the recipient reinstalled; signal-cli daemon version mismatch (method names like sendPollCreate or sendTyping vary by version); daemon websocket/rpc bridge misconfigured; sending to self which signal-cli rejects.
Related errors
- webhook reply failed ({status}): {err}
- Signal channel requires the `channel-signal` feature
- Download failed ({}): {url}
- QQ WebSocket connection closed: invalid session (fresh auth
- QQ WebSocket connection closed: close_code={code}, reason="{
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/83eb4ae4eef60a7a.
Report an issue: GitHub.