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.

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)

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.