Hmbown/CodeWhale · error · io::Error

<serde_json serialization error>

Error message

<serde_json serialization error>

What it means

Writing the late-usage ledger serializes the LateUsageLedger struct to JSON with serde_json::to_vec; if serialization fails, the error is wrapped into an io::Error (ErrorKind::InvalidData) whose message is the serde_json error text, e.g. 'invalid type: ...' or 'key must be a string'. In practice serde_json serialization of a plain ledger struct only fails if the data model holds values serde cannot represent, most commonly map keys that are not strings. Thrown from write_late_usage_ledger at crates/tui/src/session_manager.rs:1474.

Solutions

  1. Read the wrapped serde_json message inside this io::Error — it names the exact field/key that failed.
  2. Ensure all map keys in LateUsageLedger are Strings; convert numeric session IDs to strings before inserting.
  3. Check recent changes to the LateUsageLedger struct for fields with custom Serialize impls that can error; fix the impl or the invariant it enforces.
  4. If the ledger came from an older format migrated in memory, re-derive it from defaults rather than feeding stale structures into write_late_usage_ledger.

Example fix

// before: numeric keys make serde_json fail
let mut ledger: LateUsageLedger = Default::default();
ledger.entries.insert(session_id_u64, entry);
// after: use String keys
ledger.entries.insert(session_id_u64.to_string(), entry);
Defensive patterns

Strategy: validation

Validate before calling

// ensure ledger data is serializable before calling the writer
let probe = serde_json::to_vec(&ledger);
if let Err(e) = probe {
    eprintln!("ledger not serializable: {e}"); // fix non-string map keys / custom Serialize
}

Type guard

fn ledger_is_serializable(ledger: &LateUsageLedger) -> bool {
    serde_json::to_vec(ledger).is_ok()
}

Try / catch

match write_late_usage_ledger(path, &ledger) {
    Ok(()) => {}
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        eprintln!("ledger serialization failed: {e}"); // message carries serde_json detail
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling write_late_usage_ledger (directly or via the session manager's persist path) when the LateUsageLedger contains data serde_json cannot serialize — typically a HashMap/BTreeMap with non-string keys (e.g. u64 keys from session IDs) or a custom Serialize impl that returns an error.

Common situations: A code change introduced non-string map keys or new fields with unserializable types into LateUsageLedger; a hand-built ledger in a test/tooling path contains NaN-like or invalid values; custom Serialize implementations that emit errors for invariants (e.g. empty required keys).

Understand the failure class

Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/a5809e4e71d72b8f. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/session_manager.rs:1474

            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
            Err(error) => return Err(error),
        };
        let mut marker = Vec::with_capacity(LATE_USAGE_DELETED.len());
        tombstone
            .take(u64::try_from(LATE_USAGE_DELETED.len()).unwrap_or(u64::MAX) + 1)
            .read_to_end(&mut marker)?;
        if marker != LATE_USAGE_DELETED {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "invalid late usage deletion marker",
            ));
        }
        Ok(true)
    }

    fn write_late_usage_ledger(path: &Path, ledger: &LateUsageLedger) -> io::Result<()> {
        let bytes = serde_json::to_vec(ledger)
            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
        if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > MAX_LATE_USAGE_LEDGER_BYTES {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "late usage ledger exceeds its size bound",
            ));
        }
        write_atomic(path, &bytes)
    }

    fn load_late_usage_unlocked(path: &Path) -> io::Result<LateUsageLedger> {
        let file = match open_private_read_file(path) {
            Ok(file) => file,
            Err(error) if error.kind() == io::ErrorKind::NotFound => {
                return Ok(LateUsageLedger::default());
            }
            Err(error) => return Err(error),
        };
        let metadata = file.metadata()?;

View on GitHub (pinned to 73e0f67d83)