{"record":{"id":"d9ba4f25b04b2da4","repo":"Hmbown/CodeWhale","slug":"moonshot-cannot-force-tool-name-because-its-input-schema-is","errorCode":null,"errorMessage":"Moonshot cannot force tool '{name}' because its input schema is incompatible with this route","messagePattern":"Moonshot cannot force tool '(.+?)' because its input schema is incompatible with this route","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/chat.rs","lineNumber":1215,"sourceCode":"                }\n            }\n        }\n        // When per-tool degradation (or the caller) left no tools, omit the\n        // key entirely: an empty `tools` array — or a `tool_choice` pointing\n        // at a dropped tool — is itself a fresh 400 on strict providers.\n        if !chat_tools.is_empty() {\n            body[\"tools\"] = json!(chat_tools);\n        }\n    }\n    if should_send_tool_choice_for_chat(provider, request.reasoning_effort.as_deref())\n        && let Some(choice) = request.tool_choice.as_ref()\n        && let Some(mapped) = map_tool_choice_for_chat(choice)\n    {\n        if matches!(provider, crate::config::ApiProvider::Moonshot)\n            && let Some(name) = mapped.pointer(\"/function/name\").and_then(Value::as_str)\n            && omitted_tool_names.iter().any(|omitted| omitted == name)\n        {\n            bail!(\n                \"Moonshot cannot force tool '{name}' because its input schema is incompatible with this route\"\n            );\n        }\n        if body.get(\"tools\").is_some() {\n            body[\"tool_choice\"] = mapped;\n        }\n    }\n    apply_route_reasoning_controls(\n        &mut body,\n        provider,\n        base_url,\n        &model,\n        request.reasoning_effort.as_deref(),\n    );\n    apply_direct_moonshot_k3_fixed_sampling(&mut body, provider, base_url, &model);\n    apply_kimi_code_fixed_sampling(&mut body, provider, base_url, &model);\n\n    // Bulletproof final sanitizer: walk the wire payload and force","sourceCodeStart":1197,"sourceCodeEnd":1233,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/client/chat.rs#L1197-L1233","documentation":"When the chat route's tool filter omitted a tool (because its input schema is not representable on this wire), a forced `tool_choice` naming that tool would ask Moonshot to call a tool that was never advertised — which the route rejects. `build_chat_wire_body` detects this conflict and fails loudly instead of sending a broken body. Raised in `crates/tui/src/client/chat.rs` around line 1215.","triggerScenarios":"Building a chat request for provider Moonshot with `tool_choice` mapped to a specific function whose name appears in `omitted_tool_names` — i.e. `map_tool_choice_for_chat` produced `{\"function\":{\"name\":...}}` for a tool that was dropped from `body[\"tools\"]` due to schema incompatibility.","commonSituations":"A tool whose JSON schema uses constructs Moonshot's route can't encode (e.g. complex anyOf/nested formats) combined with an explicit 'force tool X' request; config where tool_choice is pinned while tool filtering silently removed the tool.","solutions":["Remove or relax the forced `tool_choice` to `auto`/`none` so the omitted tool isn't demanded.","Simplify the tool's input schema so it survives the route's tool filtering and is actually advertised.","Use a different provider/route whose wire format supports the tool's schema.","If the tool is essential, register a schema-compatible variant of it for Moonshot routes."],"exampleFix":"// before\nbody[\"tool_choice\"] = json!({\"type\": \"function\", \"function\": {\"name\": \"run_query\"}});\n// after\nbody[\"tool_choice\"] = json!(\"auto\"); // run_query was omitted on this Moonshot route","handlingStrategy":"validation","validationCode":"fn forced_tool_available(choice: &Value, advertised: &[&str]) -> Result<(), String> {\n    if let Some(name) = choice.pointer(\"/function/name\").and_then(Value::as_str) {\n        if !advertised.contains(&name) {\n            return Err(format!(\"tool '{name}' was filtered out; cannot force it\"));\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_forced_named_tool(tool_choice: &Value) -> Option<&str> {\n    tool_choice.pointer(\"/function/name\").and_then(Value::as_str)\n}","tryCatchPattern":"match build_chat_wire_body(...) {\n    Err(e) if e.to_string().contains(\"cannot force tool\") => {\n        body[\"tool_choice\"] = json!(\"auto\"); // degrade instead of failing\n        retry_request(body)\n    }\n    other => other,\n}","preventionTips":["Check whether a forced tool survived the route's schema filtering before pinning tool_choice.","Simplify tool input schemas for Moonshot routes.","Fall back to tool_choice:auto when a named tool was omitted."],"tags":["tools","moonshot","schema"],"backgroundTag":"unsupported-operation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}