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

interaction followup edit failed ({status}): {err}

Error message

interaction followup edit failed ({status}): {err}

What it means

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).

Source

Thrown at crates/zeroclaw-channels/src/discord/interaction.rs:198

    // interactive action rows (EPIC B). `to_rest_json` omits whichever are empty,
    // so a plain text reply stays byte-identical.
    let payload = DiscordOutgoing {
        content: Some(content.to_string()),
        embeds: embeds.to_vec(),
        components: components.to_vec(),
        ..Default::default()
    };
    // without_url: transport errors embed the token-bearing URL.
    let resp = client
        .patch(&url)
        .json(&payload.to_rest_json())
        .send()
        .await
        .map_err(reqwest::Error::without_url)?;
    if !resp.status().is_success() {
        let status = resp.status();
        let err = resp.text().await.unwrap_or_default();
        anyhow::bail!("interaction followup edit failed ({status}): {err}");
    }
    Ok(())
}

/// Post an additional interaction followup message
/// (`POST {api_base}/webhooks/{app_id}/{token}`), used to deliver the answer
/// chunks beyond the first when a reply exceeds Discord's 2000-char limit.
pub(crate) async fn discord_post_interaction_followup(
    client: &reqwest::Client,
    app_id: &str,
    interaction_token: &str,
    api_base: &str,
    content: &str,
) -> anyhow::Result<()> {
    let url = format!("{api_base}/webhooks/{app_id}/{interaction_token}");
    let resp = client
        .post(&url)
        .json(&DiscordOutgoing::text(content).to_rest_json())

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Match the embedded status: 404/Unknown interaction → token expired; 10008 Unknown Message → placeholder deleted; 400 50035 → payload validation
  2. Finish interaction answers well inside 15 minutes — run long tools after delivering the answer
  3. On deleted @original, fall back to posting a fresh followup message (the webhook accepts new messages)
  4. Clamp embed sizes and validate component markers before delivery

Example fix

// before: only the @original edit
discord_edit_interaction_response(&client, app, tok, base, content, &embeds, &rows).await?;

// after: fall back to a followup post when @original is gone
if discord_edit_interaction_response(&client, app, tok, base, content, &embeds, &rows).await.is_err() {
    discord_post_interaction_followup(&client, app, tok, base, content).await?;
}
Defensive patterns

Strategy: fallback

Try / catch

if discord_edit_interaction_response(&client, app, tok, base, content, &embeds, &rows).await.is_err() {
    // @original gone or token dead — a fresh followup still works while the token lives
    discord_post_interaction_followup(&client, app, tok, base, content).await?;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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