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

  1. Make generation fast: local prefix index, cache, or precomputed choice list — target well under 1 second
  2. Drop stale requests: when a newer keystroke arrived, skip answering the older interaction
  3. Validate choice name/value lengths before sending (string name/value ≤100)
  4. 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

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


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