{"record":{"id":"57e8571e88fdd7ae","repo":"zeroclaw-labs/zeroclaw","slug":"post-failed-status-body","errorCode":null,"errorMessage":"post failed ({status}): {body}","messagePattern":"post failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/bluesky.rs","lineNumber":374,"sourceCode":"                created_at: now,\n                reply,\n            },\n        };\n\n        let resp = client\n            .post(format!(\"{BSKY_API_BASE}/com.atproto.repo.createRecord\"))\n            .bearer_auth(&token)\n            .json(&request)\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!(\"post failed ({status}): {body}\");\n        }\n\n        Ok(())\n    }\n\n    async fn listen(&self, tx: tokio::sync::mpsc::Sender<ChannelMessage>) -> Result<()> {\n        // Initial auth\n        self.create_session().await?;\n\n        ::zeroclaw_log::record!(\n            INFO,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note),\n            &format!(\"channel listening as @{}...\", self.handle)\n        );\n\n        loop {\n            tokio::time::sleep(POLL_INTERVAL).await;\n","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/bluesky.rs#L356-L392","documentation":"Posting via com.atproto.repo.createRecord returned a non-2xx response; the HTTP status and body are embedded. send() resolves the access JWT, builds a PostRecord (with a reply reference when the recipient is in \"uri|cid\" form), truncates text to 300 chars, and this error means the AT Protocol API rejected the resulting record.","triggerScenarios":"Channel::send on a Bluesky channel when the reply reference is invalid (recipient not a well-formed uri|cid of an existing post, or the parent post was deleted), the access JWT expired or was invalidated (401), the record fails schema validation (400), or the account is rate limited (429).","commonSituations":"Replying to a deleted or moderated post whose reply_target went stale; bursts of posts hitting API rate limits; tokens invalidated server-side between polls; recipient strings reused from other channel formats.","solutions":["Read the embedded status: 400 = invalid record (usually a stale or malformed reply ref), 401 = auth (refresh the session), 429 = rate limit (back off).","If the parent post was deleted, retry as a plain post by dropping the uri|cid reply suffix from the recipient.","On 401, force a fresh create_session so both JWTs are replaced.","Throttle outbound posts — the channel truncates to 300 chars but does not rate-limit for you."],"exampleFix":"// before: replying to a possibly-deleted parent\nch.send(&SendMessage::new(text, reply_target)).await?;\n\n// after: degrade to a fresh post when the reply ref is rejected\nif let Err(e) = ch.send(&SendMessage::new(text, reply_target)).await {\n    if e.to_string().contains(\"post failed (400\") {\n        return ch.send(&SendMessage::new(text, \"\")).await;\n    }\n    return Err(e);\n}","handlingStrategy":"retry","validationCode":"fn reply_target_is_wellformed(recipient: &str) -> bool {\n    match recipient.split_once('|') {\n        Some((uri, cid)) => uri.starts_with(\"at://\") && !cid.is_empty(),\n        None => true, // plain post, no reply reference\n    }\n}","typeGuard":null,"tryCatchPattern":"match ch.send(&msg).await {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"(429\") => {\n        tokio::time::sleep(Duration::from_secs(60)).await;\n        ch.send(&msg).await?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate reply targets (at:// URI + CID) before sending","Drop replies to deleted posts instead of retrying them","Refresh JWTs on 401 rather than resending the same token","Throttle outbound posts below AT Protocol rate limits"],"tags":["rust","bluesky","atproto","http-api","post","rate-limit"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}