{"record":{"id":"18e1eb7ed9b84dda","repo":"zeroclaw-labs/zeroclaw","slug":"desired-command-set-is-not-an-array","errorCode":null,"errorMessage":"desired command set is not an array","messagePattern":"desired command set is not an array","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/discord/slash.rs","lineNumber":472,"sourceCode":"async fn rate_limit_deadline(resp: reqwest::Response) -> i64 {\n    let now = crate::discord_slash_state::now_unix();\n    let headers = resp.headers().clone();\n    let body = resp.json::<serde_json::Value>().await.ok();\n    crate::discord_slash_state::retry_after_deadline(&headers, body.as_ref(), now)\n}\n\npub(crate) async fn reconcile_slash_commands(\n    client: &reqwest::Client,\n    bot_token: &str,\n    app_id: &str,\n    desired: &serde_json::Value,\n    api_base: &str,\n    scope: SlashScope,\n    guild_ids: &[String],\n) -> anyhow::Result<ReconcileOutcome> {\n    let auth = format!(\"Bot {bot_token}\");\n    let Some(desired) = desired.as_array() else {\n        anyhow::bail!(\"desired command set is not an array\");\n    };\n    let desired_names: std::collections::HashSet<&str> = desired\n        .iter()\n        .filter_map(|c| c.get(\"name\").and_then(|n| n.as_str()))\n        .collect();\n\n    let global_base = format!(\"{api_base}/applications/{app_id}/commands\");\n    let guild_base = |g: &str| format!(\"{api_base}/applications/{app_id}/guilds/{g}/commands\");\n    let (active, inactive): (Vec<String>, Vec<String>) = match scope {\n        SlashScope::Global => (\n            vec![global_base],\n            guild_ids.iter().map(|g| guild_base(g)).collect(),\n        ),\n        SlashScope::Guild => (\n            guild_ids.iter().map(|g| guild_base(g)).collect(),\n            vec![global_base],\n        ),\n    };","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/discord/slash.rs#L454-L490","documentation":"reconcile_slash_commands takes the desired command set as a serde_json::Value and immediately requires it to be a JSON array of command objects (each with a name). In the shipped runtime the desired set is built internally from skill specs plus the built-in ask command, so this bail means a forked/patched builder or a hand-built payload produced a non-array.","triggerScenarios":"Calling reconcile_slash_commands with a desired value that is an object, string, or null instead of an array — e.g. a fork building desired straight from config whose schema drifted, or a test fixture with the wrong top-level shape.","commonSituations":"Forks that derive the command set from user config where the config shape changed; hand-written test payloads wrapping the commands in an object instead of the array itself.","solutions":["Make the desired set a JSON array of command objects: [{\\\"name\\\": ..., ...}, ...]","Validate that the skills → command mapping emits an array before reconciling","Fix test fixtures that pass an object with a commands key instead of the bare array"],"exampleFix":"// before\nlet desired = serde_json::json!({ \"commands\": specs }); // object: reconcile bails\n\n// after\nlet desired = serde_json::json!(specs); // array of command objects","handlingStrategy":"type-guard","validationCode":"if !desired.is_array() {\n    anyhow::bail!(\"desired command set must be a JSON array of command objects\");\n}","typeGuard":"// Rust — narrow the desired set before reconciling\nfn is_valid_desired_set(desired: &serde_json::Value) -> bool {\n    desired.as_array().is_some_and(|cmds| {\n        cmds.iter().all(|c| c.get(\"name\").is_some_and(|n| n.is_string()))\n    })\n}","tryCatchPattern":null,"preventionTips":["Keep command-set builders returning Vec<Value> and serialize the vector itself, never a wrapper object","Assert the top-level shape in any test that feeds reconcile_slash_commands"],"tags":["discord","slash-commands","json-validation","invariant"],"backgroundTag":"schema-validation-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}