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

summary.json is empty (0 bytes)

Error message

summary.json is empty (0 bytes): {}

What it means

read_summary reads a session's summary.json and rejects a zero-length file before attempting JSON parsing. This library writes summary.json atomically, so a 0-byte file means the file was truncated or created by a non-atomic writer (or a crash/interrupted write left a stub). The io::ErrorKind::InvalidData error carries the file path to help locate the corrupt file.

Solutions

  1. Delete the empty summary.json (or restore it from backup) so the session can regenerate it on next write
  2. Re-run the operation; readers that fail on one session's empty summary are usually scanning multiple sessions — ensure the code skips unreadable sessions instead of aborting
  3. Verify writes go through write_summary_atomic/write_bytes_atomic, never plain fs::write to the final path
  4. Check disk space and filesystem health if truncation happened repeatedly

Example fix

// before
let summary = read_summary(&path)?;
// after
let summary = match read_summary(&path) {
    Ok(s) => s,
    Err(e) if e.kind() == io::ErrorKind::InvalidData => { /* skip/log corrupt summary */ continue; }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: try-catch

Validate before calling

let meta = std::fs::metadata(&path)?;
if meta.len() == 0 { /* skip or delete corrupt summary */ }

Type guard

fn is_readable_summary(path: &Path) -> bool {
    std::fs::read(path).map(|b| !b.is_empty()).unwrap_or(false)
}

Try / catch

match read_summary(&path) {
    Ok(s) => s,
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        let _ = std::fs::remove_file(&path); // drop corrupt file, regenerate later
        Summary::default()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling read_summary (directly or via most_recent_local_summary_for_cwd_in_view, repair_worktree_identity, read_modify_write, or the auto-title paths) on a summary.json that exists but is 0 bytes — e.g. after a crash during a legacy non-atomic write, an external tool touching the file, or a filesystem sync that truncated it.

Common situations: Disk-full during an old non-atomic write; a user or backup tool creating an empty summary.json; crash between file creation and content flush; cloning/syncing session dirs with a sync tool that creates placeholder empty files.

Related errors


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

Appendix: source

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

    let mut summary = read_summary(summary_path)?;
    let absent_title_applied = summary.apply_patch(patch, Utc::now());
    write_summary_atomic(summary_path, &summary)?;
    Ok(absent_title_applied)
}

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) };
}

View on GitHub (pinned to bc7f02eddd)