{"record":{"id":"f0d9ac331eff5aa4","repo":"Hmbown/CodeWhale","slug":"late-usage-ledger-exceeds-its-size-bound","errorCode":null,"errorMessage":"late usage ledger exceeds its size bound","messagePattern":"late usage ledger exceeds its size bound","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/session_manager.rs","lineNumber":1476,"sourceCode":"        };\n        let mut marker = Vec::with_capacity(LATE_USAGE_DELETED.len());\n        tombstone\n            .take(u64::try_from(LATE_USAGE_DELETED.len()).unwrap_or(u64::MAX) + 1)\n            .read_to_end(&mut marker)?;\n        if marker != LATE_USAGE_DELETED {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"invalid late usage deletion marker\",\n            ));\n        }\n        Ok(true)\n    }\n\n    fn write_late_usage_ledger(path: &Path, ledger: &LateUsageLedger) -> io::Result<()> {\n        let bytes = serde_json::to_vec(ledger)\n            .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;\n        if u64::try_from(bytes.len()).unwrap_or(u64::MAX) > MAX_LATE_USAGE_LEDGER_BYTES {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"late usage ledger exceeds its size bound\",\n            ));\n        }\n        write_atomic(path, &bytes)\n    }\n\n    fn load_late_usage_unlocked(path: &Path) -> io::Result<LateUsageLedger> {\n        let file = match open_private_read_file(path) {\n            Ok(file) => file,\n            Err(error) if error.kind() == io::ErrorKind::NotFound => {\n                return Ok(LateUsageLedger::default());\n            }\n            Err(error) => return Err(error),\n        };\n        let metadata = file.metadata()?;\n        if metadata.len() > MAX_LATE_USAGE_LEDGER_BYTES {\n            return Err(io::Error::new(","sourceCodeStart":1458,"sourceCodeEnd":1494,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/session_manager.rs#L1458-L1494","documentation":"write_late_usage_ledger enforces a 1 MiB cap (MAX_LATE_USAGE_LEDGER_BYTES) on the serialized ledger JSON. If serde_json::to_vec produces more bytes than the cap, the write is refused with this InvalidData io::Error instead of writing an unbounded file. This keeps the ledger a bounded, replayable file rather than letting it grow without limit. Thrown from crates/tui/src/session_manager.rs:1476.","triggerScenarios":"Persisting a LateUsageLedger whose accumulated entries exceed 1024*1024 bytes of JSON — many tracked sessions with long paths/IDs, or entries never pruned after long use, so to_vec exceeds MAX_LATE_USAGE_LEDGER_BYTES before write_atomic is called.","commonSituations":"Long-running installs where the ledger accumulates thousands of late-usage entries; sessions with very long file paths inflating per-entry size; an earlier version or fork wrote a larger ledger and newer code now refuses to rewrite it; tests that build a huge synthetic ledger.","solutions":["Prune old/finished entries from the LateUsageLedger before writing so the JSON stays under 1 MiB.","Check the ledger file size on disk (ls -l) and delete/reset the ledger file if its contents are disposable; Codewhale will start from a default ledger.","Compact representation: shorten stored data (store IDs instead of full paths) if entries are inherently large.","If 1 MiB is genuinely too small for legitimate data, raise MAX_LATE_USAGE_LEDGER_BYTES deliberately and document the change — do not remove the bound."],"exampleFix":"// before: write everything, unbounded growth\nledger.entries.insert(key, entry);\nwrite_late_usage_ledger(path, &ledger)?;\n// after: prune stale entries first\nledger.entries.retain(|_, e| e.last_seen >= cutoff);\nwrite_late_usage_ledger(path, &ledger)?;","handlingStrategy":"validation","validationCode":"let bytes = serde_json::to_vec(&ledger).map_err(io::Error::other)?;\nif bytes.len() > 1024 * 1024 {\n    ledger.entries.retain(|_, e| e.last_seen >= cutoff); // prune, then retry\n}","typeGuard":"fn ledger_fits(ledger: &LateUsageLedger) -> bool {\n    serde_json::to_vec(ledger).map_or(false, |b| b.len() <= 1024 * 1024)\n}","tryCatchPattern":"if let Err(e) = write_late_usage_ledger(path, &ledger) {\n    if e.to_string().contains(\"exceeds its size bound\") {\n        let mut pruned = ledger.clone();\n        pruned.entries.retain(|_, v| v.last_seen >= cutoff);\n        write_late_usage_ledger(path, &pruned)?;\n    } else { return Err(e); }\n}","preventionTips":["Prune stale entries on every persist instead of letting the ledger grow monotonically.","Store compact identifiers (IDs) rather than full paths in ledger entries.","Check the ledger file size periodically (ls -l) in long-running installs.","Never disable the size bound; raise the constant deliberately if data legitimately grows."],"tags":["io","size-limit","ledger","session-state"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}