zeroclaw-labs/zeroclaw · error · anyhow::Error
interaction defer failed ({status}): {err}
Error message
interaction defer failed ({status}): {err} What it means
discord_defer_interaction POSTs a type-5 DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE callback to /interactions/{id}/{token}/callback to acknowledge within Discord's 3-second window. Non-2xx lands here with status and body. Classic causes: unknown or already-consumed interaction token (404/400) or a handler too slow to meet the window — the user then sees "The application did not respond". Transport errors are mapped through reqwest::Error::without_url so the token-bearing URL never leaks into logs.
Source
Thrown at crates/zeroclaw-channels/src/discord/interaction.rs:48
interaction_token: &str,
) -> anyhow::Result<()> {
let url = format!(
"https://discord.com/api/v10/interactions/{interaction_id}/{interaction_token}/callback"
);
// type 5 = DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE
let body = json!({ "type": 5 });
// without_url: reqwest transport errors embed the full request URL,
// which here contains the interaction token (a live credential).
let resp = client
.post(&url)
.json(&body)
.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 defer failed ({status}): {err}");
}
Ok(())
}
/// Open a modal in response to a button/slash interaction (callback type 9).
/// The caller registers the modal's `custom_id` in the pending registry as a
/// resolve-into-turn so the eventual type-5 submit resolves it. Driven by the
/// `OpenModal` dispatch arm (a `[COMPONENTS:…]` modal button click).
pub(crate) async fn discord_open_modal(
client: &reqwest::Client,
interaction_id: &str,
interaction_token: &str,
modal: &super::components::DiscordModal,
) -> anyhow::Result<()> {
let Some(data) = modal.to_api() else {
anyhow::bail!("modal custom_id exceeds Discord's 100-char limit; cannot open");
};
let url = format!(View on GitHub (pinned to 88bb9c8533)
Solutions
- Defer first, work second — move the defer to the very top of the interaction dispatch arm
- If the body says Unknown interaction or already acknowledged, do not re-defer; answer via the followup webhook or drop the turn
- Keep the pre-ack path free of awaits that can block (remote calls, lock contention)
- Confirm gateway events arrive fresh (healthy connection, no long resume gaps)
Example fix
// before: heavy work then ack let answer = run_agent(prompt).await?; discord_defer_interaction(&client, &id, &token).await?; // after: ack inside the 3s window, then work discord_defer_interaction(&client, &id, &token).await?; let answer = run_agent(prompt).await?;
Defensive patterns
Strategy: try-catch
Try / catch
if let Err(e) = discord_defer_interaction(&client, &id, &token).await {
// token consumed/expired: do NOT re-defer — answer via the followup webhook or drop the turn
tracing::warn!("defer failed: {e}");
} Prevention
- Ack within 3 seconds: defer before any slow work
- Send exactly one initial response per interaction
- Treat defer failure as "turn unanswerable", not a retry candidate
When it happens
Trigger: Slow work before the defer exceeds ~3s from interaction creation; deferring the same interaction twice (only one initial response is allowed); id/token mismatch or events replayed after a gateway resume; dispatch arm blocked on locks or cold caches.
Common situations: Agent performing tool calls or slow LLM inference before acknowledging; startup jank delaying the first callback; replaying captured interaction payloads in dev.
Related errors
- modal open failed ({status}): {err}
- interaction autocomplete answer failed ({status}): {err}
- interaction reject failed ({status}): {err}
- interaction followup edit failed ({status}): {err}
- interaction followup post failed ({status}): {err}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/2b283b08ad3f09c7.
Report an issue: GitHub.