xai-org/grok-build · error

JSON object must have a "content" field

Error message

JSON object must have a "content" field

What it means

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.

Source

Thrown at crates/codegen/xai-grok-pager/src/headless/cli.rs:110

/// Parse ACP content blocks from an array (`[...]`) or typed wrapper (`{"type":"acp","content":[...]}`).
fn parse_prompt_json(json_str: &str) -> anyhow::Result<Vec<acp::ContentBlock>> {
    let value: serde_json::Value =
        serde_json::from_str(json_str).map_err(|e| anyhow::anyhow!("Invalid JSON: {e}"))?;

    let blocks: Vec<acp::ContentBlock> = match value {
        serde_json::Value::Array(_) => serde_json::from_value(value)
            .map_err(|e| anyhow::anyhow!("Invalid ACP content blocks: {e}"))?,

        serde_json::Value::Object(ref map) => {
            let format_type = map.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
                anyhow::anyhow!(
                    "JSON object must have a \"type\" field \
                         (e.g., {{\"type\": \"acp\", \"content\": [...]}})"
                )
            })?;
            let content = map
                .get("content")
                .ok_or_else(|| anyhow::anyhow!("JSON object must have a \"content\" field"))?;

            match format_type {
                "acp" => serde_json::from_value(content.clone()).map_err(|e| {
                    anyhow::anyhow!("Invalid ACP content blocks in \"content\": {e}")
                })?,
                other => anyhow::bail!(
                    "Unsupported prompt format type: \"{other}\". Supported types: \"acp\""
                ),
            }
        }

        _ => {
            anyhow::bail!("Expected JSON array or {{\"type\": \"...\", \"content\": [...]}} object")
        }
    };

    if blocks.is_empty() {
        anyhow::bail!("content blocks array is empty");

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Add a "content" key whose value is the array of content blocks.
  2. Check for key typos (content vs blocks/messages).
  3. Alternatively pass the block array directly at the top level, skipping the wrapper.
  4. Validate the wrapper shape ({type,content}) before invoking from_json.

Example fix

// before
{"type":"acp","blocks":[{"type":"text","text":"hi"}]}
// after
{"type":"acp","content":[{"type":"text","text":"hi"}]}
Defensive patterns

Strategy: validation

Validate before calling

fn wrapper_has_content(s: &str) -> bool {
    serde_json::from_str::<serde_json::Value>(s)
        .ok()
        .and_then(|v| v.get("content").map(|_| true))
        .unwrap_or(false)
}

Type guard

fn is_complete_acp_wrapper(v: &serde_json::Value) -> bool {
    v.get("type").and_then(|t| t.as_str()) == Some("acp") && v.get("content").is_some()
}

Try / catch

match HeadlessPrompt::from_json(&raw) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("must have a \"content\" field") => {
        eprintln!("add a \"content\" array of blocks to the wrapper");
        std::process::exit(2);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling from_json with {"type":"acp"} or any object with a valid string "type" but no "content" key.

Common situations: 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.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/2b38baf0501c8e28. Report an issue: GitHub.