{"record":{"id":"adf751b59f453db5","repo":"xai-org/grok-build","slug":"json-object-must-have-a-type-field-e-g-type","errorCode":null,"errorMessage":"JSON object must have a \"type\" field (e.g., {\"type\": \"acp\", \"content\": [...]})","messagePattern":"JSON object must have a \"type\" field \\(e\\.g\\., (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-pager/src/headless/cli.rs","lineNumber":103,"sourceCode":"        match self {\n            Self::Text(text) => vec![acp::ContentBlock::Text(acp::TextContent::new(text))],\n            Self::Blocks(blocks) => blocks,\n        }\n    }\n}\n\n/// Parse ACP content blocks from an array (`[...]`) or typed wrapper (`{\"type\":\"acp\",\"content\":[...]}`).\nfn parse_prompt_json(json_str: &str) -> anyhow::Result<Vec<acp::ContentBlock>> {\n    let value: serde_json::Value =\n        serde_json::from_str(json_str).map_err(|e| anyhow::anyhow!(\"Invalid JSON: {e}\"))?;\n\n    let blocks: Vec<acp::ContentBlock> = match value {\n        serde_json::Value::Array(_) => serde_json::from_value(value)\n            .map_err(|e| anyhow::anyhow!(\"Invalid ACP content blocks: {e}\"))?,\n\n        serde_json::Value::Object(ref map) => {\n            let format_type = map.get(\"type\").and_then(|v| v.as_str()).ok_or_else(|| {\n                anyhow::anyhow!(\n                    \"JSON object must have a \\\"type\\\" field \\\n                         (e.g., {{\\\"type\\\": \\\"acp\\\", \\\"content\\\": [...]}})\"\n                )\n            })?;\n            let content = map\n                .get(\"content\")\n                .ok_or_else(|| anyhow::anyhow!(\"JSON object must have a \\\"content\\\" field\"))?;\n\n            match format_type {\n                \"acp\" => serde_json::from_value(content.clone()).map_err(|e| {\n                    anyhow::anyhow!(\"Invalid ACP content blocks in \\\"content\\\": {e}\")\n                })?,\n                other => anyhow::bail!(\n                    \"Unsupported prompt format type: \\\"{other}\\\". Supported types: \\\"acp\\\"\"\n                ),\n            }\n        }\n","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager/src/headless/cli.rs#L85-L121","documentation":"When the prompt JSON is an object, parse_prompt_json requires a string \"type\" field to select the format. If the object has no \"type\" key or it is not a string, this bail message is returned verbatim, including the expected wrapper example. It exists to guide users to the typed wrapper format {\"type\":\"acp\",\"content\":[...]}.","triggerScenarios":"Calling from_json with a JSON object like {\"content\":[...]}, {\"prompt\":\"...\"}, or {\"type\":123} — i.e. an object whose \"type\" key is absent or non-string.","commonSituations":"Wrapping blocks in an object but forgetting the discriminator; passing a plain config-like object as a prompt; older tooling emitting a wrapper without the \"type\" key.","solutions":["Add a string \"type\" field with value \"acp\" to the wrapper object.","Or pass the blocks as a top-level JSON array instead of an object.","Quote the type value: JSON requires \"type\":\"acp\", not type:acp.","Confirm the top-level object is the prompt wrapper, not the content block itself (a block object should be inside the content array)."],"exampleFix":"// before\n{\"content\":[{\"type\":\"text\",\"text\":\"hi\"}]}\n// after\n{\"type\":\"acp\",\"content\":[{\"type\":\"text\",\"text\":\"hi\"}]}","handlingStrategy":"validation","validationCode":"fn wrapper_has_type(s: &str) -> bool {\n    serde_json::from_str::<serde_json::Value>(s)\n        .ok()\n        .and_then(|v| v.get(\"type\").and_then(|t| t.as_str()).map(|_| true))\n        .unwrap_or(false)\n}","typeGuard":"fn is_acp_wrapper(v: &serde_json::Value) -> bool {\n    v.as_object()\n        .and_then(|o| o.get(\"type\").and_then(|t| t.as_str()))\n        .map(|t| t == \"acp\")\n        .unwrap_or(false)\n}","tryCatchPattern":"match HeadlessPrompt::from_json(&raw) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"must have a \\\"type\\\" field\") => {\n        eprintln!(\"wrap the object as {\\\"type\\\":\\\"acp\\\",\\\"content\\\":[...]}\");\n        std::process::exit(2);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always include \"type\":\"acp\" when using the object wrapper form.","Prefer the plain array form when you don't need a wrapper.","Quote all JSON keys and string values in hand-written payloads.","Add a shape check ({type,content}) to any script generating prompts."],"tags":["json","acp","cli","missing-field"],"backgroundTag":"missing-required-field","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}