{"record":{"id":"2b38baf0501c8e28","repo":"xai-org/grok-build","slug":"json-object-must-have-a-content-field","errorCode":null,"errorMessage":"JSON object must have a \"content\" field","messagePattern":"JSON object must have a \"content\" field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-pager/src/headless/cli.rs","lineNumber":110,"sourceCode":"/// 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\n        _ => {\n            anyhow::bail!(\"Expected JSON array or {{\\\"type\\\": \\\"...\\\", \\\"content\\\": [...]}} object\")\n        }\n    };\n\n    if blocks.is_empty() {\n        anyhow::bail!(\"content blocks array is empty\");","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager/src/headless/cli.rs#L92-L128","documentation":"After verifying the object's \"type\" field, parse_prompt_json requires a \"content\" key holding the block list. If the key is missing, it returns the fixed message \"JSON object must have a \\\"content\\\" field\". The library throws it so the wrapper format is enforced explicitly rather than failing deep inside serde with a confusing message.","triggerScenarios":"Calling from_json with {\"type\":\"acp\"} or any object with a valid string \"type\" but no \"content\" key.","commonSituations":"Typo like \"blocks\" or \"messages\" instead of \"content\"; truncating the wrapper when building it by hand; copying an example of the array form into an object wrapper without adding content.","solutions":["Add a \"content\" key whose value is the array of content blocks.","Check for key typos (content vs blocks/messages).","Alternatively pass the block array directly at the top level, skipping the wrapper.","Validate the wrapper shape ({type,content}) before invoking from_json."],"exampleFix":"// before\n{\"type\":\"acp\",\"blocks\":[{\"type\":\"text\",\"text\":\"hi\"}]}\n// after\n{\"type\":\"acp\",\"content\":[{\"type\":\"text\",\"text\":\"hi\"}]}","handlingStrategy":"validation","validationCode":"fn wrapper_has_content(s: &str) -> bool {\n    serde_json::from_str::<serde_json::Value>(s)\n        .ok()\n        .and_then(|v| v.get(\"content\").map(|_| true))\n        .unwrap_or(false)\n}","typeGuard":"fn is_complete_acp_wrapper(v: &serde_json::Value) -> bool {\n    v.get(\"type\").and_then(|t| t.as_str()) == Some(\"acp\") && v.get(\"content\").is_some()\n}","tryCatchPattern":"match HeadlessPrompt::from_json(&raw) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"must have a \\\"content\\\" field\") => {\n        eprintln!(\"add a \\\"content\\\" array of blocks to the wrapper\");\n        std::process::exit(2);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Use the exact key name \"content\" — avoid aliases like blocks or messages.","Copy wrapper examples verbatim instead of retyping keys.","Validate the wrapper object shape before calling from_json.","Generate wrappers with serde_json::json! to eliminate key typos."],"tags":["json","acp","missing-field","cli"],"backgroundTag":"missing-required-field","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}