zeroclaw-labs/zeroclaw · warning · anyhow::Error
interaction autocomplete answer failed ({status}): {err}
Error message
interaction autocomplete answer failed ({status}): {err} What it means
discord_answer_autocomplete answers an APPLICATION_COMMAND_AUTOCOMPLETE interaction with a type-8 callback carrying up to 25 choices (the code pre-clamps with .take(25), so count is never the failure). Non-2xx becomes this error. Autocomplete tokens are the shortest-lived interaction credentials — Discord expects the answer within roughly 3 seconds of the keystroke — so slow generation and stale requests dominate; sending type-8 to a non-autocomplete interaction also 400s.
Source
Thrown at crates/zeroclaw-channels/src/discord/interaction.rs:112
);
let rendered: Vec<serde_json::Value> = choices
.iter()
.take(25)
.map(|(name, value)| json!({ "name": name, "value": value }))
.collect();
// type 8 = APPLICATION_COMMAND_AUTOCOMPLETE_RESULT
let body = json!({ "type": 8, "data": { "choices": rendered } });
// without_url: transport errors embed the token-bearing URL.
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 autocomplete answer failed ({status}): {err}");
}
Ok(())
}
/// Extract a string option (`d.data.options[name].value`) from an
/// APPLICATION_COMMAND interaction payload. Empty string when absent.
pub(crate) fn interaction_string_option(d: &serde_json::Value, name: &str) -> String {
d.get("data")
.and_then(|x| x.get("options"))
.and_then(|o| o.as_array())
.and_then(|opts| {
opts.iter()
.find(|o| o.get("name").and_then(|n| n.as_str()) == Some(name))
})
.and_then(|o| o.get("value"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string()View on GitHub (pinned to 88bb9c8533)
Solutions
- Make generation fast: local prefix index, cache, or precomputed choice list — target well under 1 second
- Drop stale requests: when a newer keystroke arrived, skip answering the older interaction
- Validate choice name/value lengths before sending (string name/value ≤100)
- Treat failure as UX degradation — log and move on; the user simply sees no suggestions
Example fix
// before: fresh LLM call per keystroke, seconds late let choices = llm_suggest(&q).await; discord_answer_autocomplete(&client, &id, &token, &choices).await?; // after: prefix search over a local index let choices = index.search(&q, 25); discord_answer_autocomplete(&client, &id, &token, &choices).await?;
Defensive patterns
Strategy: try-catch
Try / catch
if let Err(e) = discord_answer_autocomplete(&client, &id, &token, &choices).await {
tracing::debug!("autocomplete missed: {e}"); // best-effort UX, never fatal
} Prevention
- Answer autocomplete from a local index/cache in well under a second
- Drop stale autocomplete requests when a newer keystroke arrived
- Keep choice names/values within Discord's 100-char limit
When it happens
Trigger: Option generation (LLM call, DB query) finishing after the interaction expired; answering after the user already submitted or aborted (token consumed); choice names/values exceeding Discord length limits from user data; interaction type mismatch.
Common situations: Autocomplete backed by an LLM taking seconds per keystroke; cold caches on first input; dev replay of old interaction payloads.
Related errors
- interaction defer failed ({status}): {err}
- modal open 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/3bce9572f3781856.
Report an issue: GitHub.