Hmbown/CodeWhale · error · io::Error
late usage ledger has an unsupported or unbounded shape
Error message
late usage ledger has an unsupported or unbounded shape
What it means
The ledger parsed as JSON but its shape is rejected: schema_version does not equal CURRENT_LATE_USAGE_SCHEMA_VERSION, records exceed MAX_LATE_USAGE_RECORDS_PER_SESSION, or any record's source_fingerprint/turn_fingerprint is not a valid SHA-256 fingerprint. This is a semantic validation error (InvalidData) guarding against unbounded or future-format ledgers.
Solutions
- Check the ledger's schema_version against CURRENT_LATE_USAGE_SCHEMA_VERSION in session_manager.rs and migrate or regenerate the file to the current version.
- Trim records to the per-session cap; keep the most recent MAX_LATE_USAGE_RECORDS_PER_SESSION entries.
- Fix fingerprint fields to be valid SHA-256 hex (64 lowercase hex chars) — recompute them from the source/turn content if they were altered.
- Delete the out-of-shape ledger so the session starts with a fresh, empty ledger.
Example fix
// before (rejected record)
{ "source_fingerprint": "abc123", "turn_fingerprint": "def456" }
// after (full SHA-256 hex)
{ "source_fingerprint": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "turn_fingerprint": "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752" } Defensive patterns
Strategy: validation
Validate before calling
let v: serde_json::Value = serde_json::from_slice(&raw)?;
let schema_ok = v["schema_version"].as_u64() == Some(CURRENT_LATE_USAGE_SCHEMA_VERSION as u64);
let records_ok = v["records"].as_array().map_or(false, |r| r.len() <= MAX_LATE_USAGE_RECORDS_PER_SESSION);
let fp_ok = |s: &str| s.len() == 64 && s.bytes().all(|b| b.is_ascii_hexdigit());
if !(schema_ok && records_ok) { /* migrate, trim, or reject before loading */ } Type guard
fn is_sha256_fingerprint(s: &str) -> bool {
s.len() == 64 && s.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F'))
} Try / catch
match manager.load_late_usage(&session_id) {
Ok(l) => l,
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
// shape mismatch: archive the file, start a fresh ledger or run a schema migration
archive_and_reset(&ledger_path);
LateUsageLedger::default()
}
Err(e) => return Err(e),
} Prevention
- Keep schema_version in sync when upgrading; run a migration step on first load after a version bump.
- Enforce the record cap at append time so the file never exceeds MAX_LATE_USAGE_RECORDS_PER_SESSION.
- Always emit fingerprints via a single hex-encoding helper rather than ad-hoc formatting.
When it happens
Trigger: Loading a ledger whose schema_version differs from the current constant, a ledger with more records than the per-session cap, or records whose fingerprint strings are not 64-char SHA-256 hex values.
Common situations: App upgraded/downgraded so schema_version changed; a runaway loop appended thousands of records before the cap existed; fingerprints were written as empty strings, prefixed hashes, or different hash encodings by external tooling.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Codewhale terminal receipt contained a non-scalar field
- Duplicate .
- Duplicate policy identity.
- Facts must be scalar metadata, not content objects.
- Fleet task ' ' metadata.coordination_contracts must be an…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/5733c565598bea4b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_manager.rs:1526
if u64::try_from(raw.len()).unwrap_or(u64::MAX) > MAX_LATE_USAGE_LEDGER_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"late usage ledger {} exceeds its size bound",
path.display()
),
));
}
let ledger: LateUsageLedger = serde_json::from_slice(&raw)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
if ledger.schema_version != CURRENT_LATE_USAGE_SCHEMA_VERSION
|| ledger.records.len() > MAX_LATE_USAGE_RECORDS_PER_SESSION
|| ledger.records.iter().any(|record| {
!is_sha256_fingerprint(&record.source_fingerprint)
|| !is_sha256_fingerprint(&record.turn_fingerprint)
})
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"late usage ledger has an unsupported or unbounded shape",
));
}
Ok(ledger)
}
fn with_session_write_admission<T>(
&self,
session_id: &str,
write: impl FnOnce() -> io::Result<T>,
) -> io::Result<Option<T>> {
let (path, lock_path) = self.ensure_late_usage_paths(session_id)?;
let lock_file = open_private_lock_file(&lock_path)?;
let mut lock = fd_lock::RwLock::new(lock_file);
let _guard = lock.write()?;
if Self::late_usage_is_deleted(&path)? {
return Ok(None);View on GitHub (pinned to 73e0f67d83)