Hmbown/CodeWhale · error · io::Error
<dynamic: wrapped serde_json parse error>
Error message
<dynamic: wrapped serde_json parse error>
What it means
After the 64 KiB size check, read_evidence_metadata_file parses the sidecar bytes with serde_json::from_slice::<LegacySpilloverOwnership/EvidenceMetadata>; any serde parse failure is wrapped in io::ErrorKind::InvalidData with the serde error as its source. The sidecar is not valid JSON for the expected schema.
Solutions
- Read the wrapped serde error to identify the exact offset/field that failed to parse.
- Delete the corrupt sidecar and let publish_evidence_metadata regenerate it from the artifact.
- If the file predates a schema change, migrate or regenerate it with the version that wrote it.
- Verify writers use write_atomic so readers never see truncated JSON.
Example fix
// before
cat meta.json # "{'handle': ..." — trailing garbage after truncated write
// after
rm meta.json # next publish_evidence_metadata writes fresh, atomically Defensive patterns
Strategy: try-catch
Validate before calling
// shell sanity check before relying on the sidecar python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$SIDECAR" || echo "corrupt sidecar; regenerate"
Try / catch
// rust
match read_evidence_metadata(&handle) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
eprintln!("sidecar JSON invalid ({e}); regenerating from artifact");
// delete sidecar and republish
}
other => other?,
} Prevention
- Write all sidecars with atomic replace so readers never see truncated JSON.
- Version the sidecar schema and migrate old files instead of letting them fail deserialization.
- Never hand-edit generated metadata files.
When it happens
Trigger: read_evidence_metadata encountering a sidecar that is empty, truncated (e.g. a crash mid-write without atomic replace), or written under an older/different schema that no longer deserializes into the expected struct.
Common situations: Partial file from a non-atomic write or power loss; schema evolution renamed/removed a required field; manually edited JSON with a syntax error; wrong file passed as a metadata sidecar.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- <dynamic: wrapped serde_json serialization error>
- (serde_json deserialization error wrapped as…
- Cannot parse portable data; use JSON for OpenCode or plain…
- deserialize
- <dynamic: wrapped serde_json serialization error>
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/8b9cf7411f2c086f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/large_output_router.rs:332
/// Bounded, no-follow read shared by publication/replay and authenticated HTTP
/// retrieval. The caller chooses the existing session-root authority.
pub(crate) fn read_evidence_metadata_file(
file: &crate::fleet::files::WorkspaceFile,
) -> io::Result<EvidenceArtifact> {
use std::io::Read;
const MAX_MANIFEST_BYTES: u64 = 64 * 1024;
let mut raw = Vec::new();
file.open_file()?
.take(MAX_MANIFEST_BYTES + 1)
.read_to_end(&mut raw)?;
if raw.len() as u64 > MAX_MANIFEST_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"evidence metadata exceeds limit",
));
}
serde_json::from_slice(&raw).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
}
#[must_use]
pub fn unix_millis_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
.try_into()
.unwrap_or(u64::MAX)
}
#[must_use]
pub fn evidence_is_expired(artifact: &EvidenceArtifact, now_ms: u64) -> bool {
artifact.retention_state == EvidenceRetentionState::Expired
|| now_ms > artifact.retain_until_unix_ms
}
View on GitHub (pinned to 73e0f67d83)