{"record":{"id":"544bd48bf91b873f","repo":"zeroclaw-labs/zeroclaw","slug":"interaction-followup-post-failed-status-err","errorCode":null,"errorMessage":"interaction followup post failed ({status}): {err}","messagePattern":"interaction followup post failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/discord/interaction.rs","lineNumber":223,"sourceCode":"/// chunks beyond the first when a reply exceeds Discord's 2000-char limit.\npub(crate) async fn discord_post_interaction_followup(\n    client: &reqwest::Client,\n    app_id: &str,\n    interaction_token: &str,\n    api_base: &str,\n    content: &str,\n) -> anyhow::Result<()> {\n    let url = format!(\"{api_base}/webhooks/{app_id}/{interaction_token}\");\n    let resp = client\n        .post(&url)\n        .json(&DiscordOutgoing::text(content).to_rest_json())\n        .send()\n        .await\n        .map_err(reqwest::Error::without_url)?;\n    if !resp.status().is_success() {\n        let status = resp.status();\n        let err = resp.text().await.unwrap_or_default();\n        anyhow::bail!(\"interaction followup post failed ({status}): {err}\");\n    }\n    Ok(())\n}\n\n#[cfg(test)]\nmod embed_reply_tests {\n    use super::*;\n    use wiremock::matchers::{body_json, body_partial_json, method, path};\n    use wiremock::{Mock, MockServer, ResponseTemplate};\n\n    #[tokio::test]\n    async fn slash_reply_attaches_embeds_to_the_original_edit() {\n        let server = MockServer::start().await;\n        Mock::given(method(\"PATCH\"))\n            .and(path(\"/webhooks/app/tok/messages/@original\"))\n            .and(body_partial_json(serde_json::json!({\n                \"content\": \"see below\",\n                \"embeds\": [{ \"title\": \"Report\" }]","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/discord/interaction.rs#L205-L241","documentation":"discord_post_interaction_followup POSTs content chunks beyond the first (replies over Discord's 2000-char limit) to {api_base}/webhooks/{app_id}/{token}. Non-2xx becomes this error. Beyond the shared token-expiry mode, this endpoint is the one most exposed to Discord rate limits (429) during multi-chunk bursts and to 50035 when a chunk itself exceeds 2000 chars after marker expansion.","triggerScenarios":"Several followup posts in quick succession hitting per-channel rate limits without honoring Retry-After; a chunk over 2000 chars after embed/component markers are expanded; token older than 15 minutes by the time later chunks send.","commonSituations":"Verbose agent outputs with large tool logs; chunk sends without inter-chunk delay; late chunks after a long-running tool call crosses the TTL.","solutions":["Honor 429: read retry_after from the response and delay before sending remaining chunks","Re-chunk to ≤2000 chars after marker expansion, not before","Deliver all chunks within the 15-minute token window — summarize if tools run long","On 404 stop the loop: remaining chunks cannot reach that token"],"exampleFix":"// before: fire all chunks back-to-back\nfor chunk in chunks {\n    discord_post_interaction_followup(&client, app, tok, base, chunk).await?;\n}\n\n// after: honor rate limits\nfor chunk in chunks {\n    if let Err(e) = discord_post_interaction_followup(&client, app, tok, base, chunk).await {\n        if let Some(d) = parse_retry_after(&e) { tokio::time::sleep(d).await; continue; }\n        break;\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for chunk in chunks {\n    if let Err(e) = discord_post_interaction_followup(&client, app, tok, base, chunk).await {\n        if let Some(d) = parse_retry_after(&e) {\n            tokio::time::sleep(d).await;\n            continue; // honor 429 Retry-After, then resend this chunk\n        }\n        break; // 404/expired token — remaining chunks are undeliverable\n    }\n}","preventionTips":["Honor 429 Retry-After on multi-chunk followups","Chunk to ≤2000 chars after marker expansion","Send every chunk inside the 15-minute window"],"tags":["discord","interactions","followup","rate-limit","chunking"],"backgroundTag":"discord-interaction-followup-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}