xai-org/grok-build · error · std::io::Error

<serde_json deserialization error for summary.json>

Error message

<serde_json deserialization error for summary.json>

What it means

read_summary deserializes the summary.json bytes into the Summary struct with serde_json; any shape mismatch becomes io::ErrorKind::InvalidData wrapping the serde error. The file exists and is non-empty but its JSON does not match the current Summary schema.

Source

Thrown at crates/codegen/xai-grok-shell/src/session/storage/summary_write.rs:414

fn open_lock_file(path: &Path) -> io::Result<File> {
    OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .truncate(false)
        .open(path)
}

fn read_summary(path: &Path) -> io::Result<Summary> {
    let bytes = std::fs::read(path)?;
    if bytes.is_empty() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            format!("summary.json is empty (0 bytes): {}", path.display()),
        ));
    }
    serde_json::from_slice::<Summary>(&bytes)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

fn write_summary_atomic(summary_path: &Path, summary: &Summary) -> io::Result<()> {
    let bytes = serde_json::to_vec_pretty(summary)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
    crate::session::storage::write_bytes_atomic(summary_path, &bytes)
}

#[cfg(test)]
thread_local! {
    static RESTORE_MTIME_FAULT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}

#[cfg(test)]
pub(crate) fn fail_next_restore_summary_mtime() {
    RESTORE_MTIME_FAULT.set(true);
}

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Read the wrapped serde error to identify the offending field, then fix or regenerate the summary.json
  2. Delete the file and let the app recreate it, or restore from backup
  3. Make newly added Summary fields #[serde(default)] so older files still deserialize
  4. Pin/align app versions so writer and reader agree on the Summary schema

Example fix

// before
#[derive(Serialize, Deserialize)]
struct Summary { title: String, new_field: String }
// after
#[derive(Serialize, Deserialize)]
struct Summary {
    title: String,
    #[serde(default)]
    new_field: String,
}
Defensive patterns

Strategy: validation

Validate before calling

let bytes = std::fs::read(&path)?;
let ok = !bytes.is_empty()
    && serde_json::from_slice::<serde_json::Value>(&bytes)
        .map(|v| v.get("title").is_some())
        .unwrap_or(false);

Type guard

fn parses_as_summary(bytes: &[u8]) -> Option<Summary> {
    serde_json::from_slice::<Summary>(bytes).ok()
}

Try / catch

match read_summary(&path) {
    Ok(s) => s,
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        eprintln!("summary.json malformed: {e}");
        Summary::default()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling read_summary on a summary.json written by an older/newer version with different fields, a hand-edited file with invalid JSON or wrong types, or a partially-written (truncated mid-way) file that is non-empty but invalid JSON.

Common situations: Version upgrade/downgrade where Summary fields were renamed or made non-optional; manual editing of session metadata; interrupted legacy writes; a different tool overwrote summary.json with its own JSON.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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