zeroclaw-labs/zeroclaw · warning · anyhow::Error
interaction reject failed ({status}): {err}
Error message
interaction reject failed ({status}): {err} What it means
discord_reject_interaction answers a policy-refused interaction immediately with a type-4 ephemeral message (flags 64) so the invoker sees why, instead of Discord's generic "The application did not respond". Non-2xx becomes this error. It fails on the same grounds as other initial callbacks: token expired/unknown (handler slower than the ~3s ack window), interaction already acknowledged (e.g. a defer also fired), or invalid body such as content over 2000 chars.
Source
Thrown at crates/zeroclaw-channels/src/discord/interaction.rs:163
);
let body = json!({
"type": 4,
"data": {
"content": message,
"flags": 64
}
});
// 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 reject failed ({status}): {err}");
}
Ok(())
}
pub(crate) async fn discord_edit_interaction_response(
client: &reqwest::Client,
app_id: &str,
interaction_token: &str,
api_base: &str,
content: &str,
embeds: &[DiscordEmbed],
components: &[DiscordActionRow],
) -> anyhow::Result<()> {
let url = format!("{api_base}/webhooks/{app_id}/{interaction_token}/messages/@original");
// No truncation: the caller chunks (deliver_interaction_answer) and this edit
// carries the first ≤2000-char chunk plus any embeds (EPIC C) and any
// interactive action rows (EPIC B). `to_rest_json` omits whichever are empty,
// so a plain text reply stays byte-identical.View on GitHub (pinned to 88bb9c8533)
Solutions
- Reject fast: authorization decisions should be local and immediate
- Guard against double-ack — exactly one of defer/reject/modal per interaction
- Keep the rejection message under 2000 chars
- On 404/expired, drop the turn: the user already saw the timeout state
Example fix
// before: remote check, then reject (may miss the 3s window)
let ok = remote_acl(user).await?;
if !ok { discord_reject_interaction(&client, &id, &token, msg).await?; }
// after: local gate, immediate ephemeral rejection
if !local_gate(user) {
discord_reject_interaction(&client, &id, &token, msg).await?;
return Ok(());
} Defensive patterns
Strategy: try-catch
Try / catch
if discord_reject_interaction(&client, &id, &token, reason).await.is_err() {
// user already saw the timeout state — log and drop the turn
} Prevention
- Make authorization decisions local so rejection is immediate
- Emit exactly one initial response (defer/reject/modal) per interaction
- Keep rejection messages under 2000 chars
When it happens
Trigger: Authorization or gate checks slow enough to miss the ack window; rejecting an interaction that was already deferred or answered; rejection message longer than 2000 chars; replaying old interactions.
Common situations: Allowlist checks hitting remote services before rejecting; double-ack bugs where defer and reject both fire; policy messages built from verbose templates.
Related errors
- interaction defer failed ({status}): {err}
- modal open failed ({status}): {err}
- interaction autocomplete answer 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/647ca0508c46d868.
Report an issue: GitHub.