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
- Read the wrapped serde_json message inside this io::Error — it names the exact field/key that failed.
- Ensure all map keys in LateUsageLedger are Strings; convert numeric session IDs to strings before inserting.
- Check recent changes to the LateUsageLedger struct for fields with custom Serialize impls that can error; fix the impl or the invariant it enforces.
- 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
- Use String keys for every map inside LateUsageLedger (serde_json cannot serialize integer map keys).
- Avoid custom Serialize impls in ledger types that can return Err for ordinary data.
- Add a round-trip unit test (to_vec then from_slice) whenever the ledger struct changes.
- Clippy/lint for HashMap<non-string, _> fields in persisted structs.
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
- <dynamic: wrapped serde_json serialization error>
- <dynamic: wrapped serde_json serialization error>
- <serde_json deserialization error>
- (serde_json serialization error wrapped as…
- serialize
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)