{"record":{"id":"a8f9d321093e6013","repo":"zeroclaw-labs/zeroclaw","slug":"dm-failed-status-body","errorCode":null,"errorMessage":"DM failed ({status}): {body}","messagePattern":"DM failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/reddit.rs","lineNumber":327,"sourceCode":"            let resp = client\n                .post(format!(\"{REDDIT_API_BASE}/api/compose\"))\n                .bearer_auth(&token)\n                .header(\"User-Agent\", USER_AGENT)\n                .form(&[\n                    (\"to\", message.recipient.as_str()),\n                    (\"subject\", subject),\n                    (\"text\", &message.content),\n                ])\n                .send()\n                .await?;\n\n            let status = resp.status();\n            if !status.is_success() {\n                let body = resp\n                    .text()\n                    .await\n                    .unwrap_or_else(|e| format!(\"<failed to read response: {e}>\"));\n                bail!(\"DM failed ({status}): {body}\");\n            }\n        }\n\n        Ok(())\n    }\n\n    async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> Result<()> {\n        // Initial auth\n        self.refresh_access_token().await?;\n\n        let scope = if self.subreddits.is_empty() {\n            String::new()\n        } else {\n            format!(\n                \"in {}\",\n                self.subreddits\n                    .iter()\n                    .map(|s| format!(\"r/{s}\"))","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/reddit.rs#L309-L345","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["On 429/'too much': back off for several minutes — compose limits are stricter than comment limits","Validate the recipient username and ensure the target account can receive PMs (verified email)","On 401, refresh the access token; on repeated 403, test manually to see if the account is blocked or restricted","Queue outbound DMs and drip-feed them instead of bursting"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(err) = channel.send(&recipient, &dm).await {\n    let msg = format!(\"{err:#}\");\n    if msg.starts_with(\"DM failed (429\") || msg.contains(\"too much\") {\n        enqueue_with_delay(dm, Duration::from_secs(600)); // drip-feed, don't fail\n        return Ok(());\n    }\n    return Err(err);\n}","preventionTips":["Queue DMs and send them with spacing — compose limits are stricter than comment limits","Verify recipient usernames and that targets can receive PMs (verified email)","On persistent 403, stop sending to that user: likely blocked or restricted"],"tags":["reddit","direct-message","rate-limit","http-status","api","channel"],"backgroundTag":"reddit-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}