{"record":{"id":"2964785c23c860df","repo":"zeroclaw-labs/zeroclaw","slug":"interaction-followup-edit-failed-status-err","errorCode":null,"errorMessage":"interaction followup edit failed ({status}): {err}","messagePattern":"interaction followup edit failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/discord/interaction.rs","lineNumber":198,"sourceCode":"    // interactive action rows (EPIC B). `to_rest_json` omits whichever are empty,\n    // so a plain text reply stays byte-identical.\n    let payload = DiscordOutgoing {\n        content: Some(content.to_string()),\n        embeds: embeds.to_vec(),\n        components: components.to_vec(),\n        ..Default::default()\n    };\n    // without_url: transport errors embed the token-bearing URL.\n    let resp = client\n        .patch(&url)\n        .json(&payload.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 edit failed ({status}): {err}\");\n    }\n    Ok(())\n}\n\n/// Post an additional interaction followup message\n/// (`POST {api_base}/webhooks/{app_id}/{token}`), used to deliver the answer\n/// 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())","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/discord/interaction.rs#L180-L216","documentation":"discord_edit_interaction_response PATCHes the deferred interaction's @original message via {api_base}/webhooks/{app_id}/{token}/messages/@original, carrying the first ≤2000-char chunk plus embeds and action rows (called from deliver_interaction_answer in the send() path for interaction: recipients). Non-2xx becomes this error. Typical causes: interaction token expired (15-minute lifetime), the @original placeholder deleted by a user, or 50035 Invalid Form Body from embed/component violations (total embed length >6000, invalid component structure, empty content with no embeds).","triggerScenarios":"Agent turn exceeding 15 minutes before the final edit; user deleting the \"thinking…\" placeholder first; [EMBED:...] payloads exceeding embed limits after prepare_outgoing_embeds; [COMPONENTS:...] markers producing invalid rows; empty content with no embeds/components.","commonSituations":"Slow agent pipelines (long tool runs) blowing the token TTL; users dismissing the deferred state; oversized embed descriptions or images; marker output from newer templates against stricter validation.","solutions":["Match the embedded status: 404/Unknown interaction → token expired; 10008 Unknown Message → placeholder deleted; 400 50035 → payload validation","Finish interaction answers well inside 15 minutes — run long tools after delivering the answer","On deleted @original, fall back to posting a fresh followup message (the webhook accepts new messages)","Clamp embed sizes and validate component markers before delivery"],"exampleFix":"// before: only the @original edit\ndiscord_edit_interaction_response(&client, app, tok, base, content, &embeds, &rows).await?;\n\n// after: fall back to a followup post when @original is gone\nif discord_edit_interaction_response(&client, app, tok, base, content, &embeds, &rows).await.is_err() {\n    discord_post_interaction_followup(&client, app, tok, base, content).await?;\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if discord_edit_interaction_response(&client, app, tok, base, content, &embeds, &rows).await.is_err() {\n    // @original gone or token dead — a fresh followup still works while the token lives\n    discord_post_interaction_followup(&client, app, tok, base, content).await?;\n}","preventionTips":["Deliver interaction answers well inside the 15-minute token TTL","Clamp embed/component payloads before delivery to avoid 50035","Log the Discord error body — 404 vs 10008 vs 50035 point to different remedies"],"tags":["discord","interactions","followup","embeds","webhook"],"backgroundTag":"discord-interaction-followup-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}