zeroclaw-labs/zeroclaw · error · anyhow::Error
listing commands failed ({})
Error message
listing commands failed ({}) What it means
During slash-command reconcile, listing the application's existing commands (global or per-guild endpoint) returned a non-success status, so the diff against the desired set cannot be computed. A 429 is special-cased earlier into a RateLimited outcome with a persisted cooldown; anything else bails here.
Source
Thrown at crates/zeroclaw-channels/src/discord/slash.rs:636
// so the fingerprint is not recorded and the next READY retries.
let mut failed_deletes = 0usize;
// `with_localizations=true` so the listing echoes back the full
// `*_localizations` dictionaries; without it Discord returns them null and
// every localized command would mismatch the projection and re-register on
// each READY (burning the daily command-create budget).
let resp = client
.get(format!("{base}?with_localizations=true"))
.header("Authorization", auth)
.send()
.await
.map_err(reqwest::Error::without_url)?;
if resp.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
return Ok(ReconcileOutcome::RateLimited {
until: rate_limit_deadline(resp).await,
});
}
if !resp.status().is_success() {
anyhow::bail!("listing commands failed ({})", resp.status());
}
let existing: Vec<serde_json::Value> =
resp.json().await.map_err(reqwest::Error::without_url)?;
for cmd in &existing {
let name = cmd.get("name").and_then(|n| n.as_str()).unwrap_or("");
if name == "ask" || desired_names.contains(name) || !is_skill_command_shape(cmd) {
continue;
}
let Some(id) = cmd.get("id").and_then(|i| i.as_str()) else {
continue;
};
let del = client
.delete(format!("{base}/{id}"))
.header("Authorization", auth)
.send()
.await
.map_err(reqwest::Error::without_url)?;
if del.status().is_success() || del.status() == reqwest::StatusCode::NOT_FOUND {View on GitHub (pinned to 88bb9c8533)
Solutions
- 401 → fix the bot token in channel config
- 403 → verify the application id matches the token's application (both must come from the same Discord app)
- 5xx → wait for the next gateway READY; reconcile is retried automatically
- In guild scope, check that guild_ids are valid snowflakes the bot can see
Defensive patterns
Strategy: retry
Try / catch
match reconcile_slash_commands(&client, &token, &app_id, &desired, base, scope, &guilds).await {
Ok(ReconcileOutcome::RateLimited { until }) => {
// cooldown persisted; wait until `until` before the next attempt
tokio::time::sleep_until(until).await;
}
Ok(ReconcileOutcome::Reconciled) => {}
Err(e) if e.to_string().contains("listing commands failed") => {
// listing failures are credential-level (401/403) or transient (5xx):
// check credentials, then let the next READY retry
tracing::warn!("slash reconcile listing failed: {e}");
}
Err(e) => return Err(e),
} Prevention
- Keep application id and bot token from the same Discord application in config
- Reconcile runs on gateway READY — transient listing failures self-heal on reconnect
- Persist the rate-limit cooldown so a 429 does not re-hammer Discord on restart
When it happens
Trigger: GET /applications/{app_id}/commands (or /guilds/{guild_id}/commands) answers 401 (invalid bot token), 403 (token does not match the application id), or 5xx — after the 429 branch has already been checked.
Common situations: Application id and bot token from different Discord apps after regenerating credentials; token revoked; Discord 5xx during the READY-triggered reconcile; invalid guild id snowflakes in guild scope.
Related errors
- {failed_deletes} stale skill command delete(s) failed; recon
- interaction defer failed ({status}): {err}
- Discord gate-prompt finalize failed ({status})
- Discord send message failed ({status}): {err}
- Discord send message with files failed ({status}): {err}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/ee86132ad9d16569.
Report an issue: GitHub.