zeroclaw-labs/zeroclaw · error · anyhow::Error

DM failed ({status}): {body}

Error message

DM failed ({status}): {body}

What it means

Raised by RedditChannel::send on the direct-message branch when POST {REDDIT_API_BASE}/api/compose returns a non-2xx status; the response body is included verbatim. Reddit reports DM failures through this endpoint with 429 rate limits ('you are doing that too much'), 400 validation errors (missing subject/recipient, NO_VERIFIED_EMAIL on the target), and 403 when the recipient has blocked the bot or the bot lacks PM permissions. It only fires for DM sends, not comment replies.

Source

Thrown at crates/zeroclaw-channels/src/reddit.rs:327

            let resp = client
                .post(format!("{REDDIT_API_BASE}/api/compose"))
                .bearer_auth(&token)
                .header("User-Agent", USER_AGENT)
                .form(&[
                    ("to", message.recipient.as_str()),
                    ("subject", subject),
                    ("text", &message.content),
                ])
                .send()
                .await?;

            let status = resp.status();
            if !status.is_success() {
                let body = resp
                    .text()
                    .await
                    .unwrap_or_else(|e| format!("<failed to read response: {e}>"));
                bail!("DM failed ({status}): {body}");
            }
        }

        Ok(())
    }

    async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> Result<()> {
        // Initial auth
        self.refresh_access_token().await?;

        let scope = if self.subreddits.is_empty() {
            String::new()
        } else {
            format!(
                "in {}",
                self.subreddits
                    .iter()
                    .map(|s| format!("r/{s}"))

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. On 429/'too much': back off for several minutes — compose limits are stricter than comment limits
  2. Validate the recipient username and ensure the target account can receive PMs (verified email)
  3. On 401, refresh the access token; on repeated 403, test manually to see if the account is blocked or restricted
  4. Queue outbound DMs and drip-feed them instead of bursting
Defensive patterns

Strategy: retry

Try / catch

if let Err(err) = channel.send(&recipient, &dm).await {
    let msg = format!("{err:#}");
    if msg.starts_with("DM failed (429") || msg.contains("too much") {
        enqueue_with_delay(dm, Duration::from_secs(600)); // drip-feed, don't fail
        return Ok(());
    }
    return Err(err);
}

Prevention

When it happens

Trigger: send() runs the else-branch (Direct message) building /api/compose with to/subject/text; any non-success status triggers the bail at reddit.rs:327.

Common situations: Bots mass-DMing users and tripping Reddit's much stricter compose rate limits; target user has no verified email (Reddit rejects composing to them); bot account too young/low-karma for the compose API; recipient username typo'd or deleted; blocked by the recipient.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/a8f9d321093e6013. Report an issue: GitHub.