{"record":{"id":"6bb5e560a886871c","repo":"Hmbown/CodeWhale","slug":"serde-json-deserialization-error-wrapped-as-io-errorkind-6bb5e5","errorCode":null,"errorMessage":"(serde_json deserialization error wrapped as io::ErrorKind::InvalidData)","messagePattern":"\\(serde_json deserialization error wrapped as io::ErrorKind::InvalidData\\)","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/truncate.rs","lineNumber":172,"sourceCode":"    crate::utils::write_atomic(&sidecar, &encoded)?;\n    Ok(sidecar)\n}\n\npub(crate) fn read_legacy_spillover_ownership(\n    payload_path: &Path,\n) -> io::Result<LegacySpilloverOwnership> {\n    let sidecar = legacy_spillover_ownership_path(payload_path);\n    if std::fs::symlink_metadata(&sidecar)?\n        .file_type()\n        .is_symlink()\n    {\n        return Err(io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            \"legacy spillover ownership sidecar must not be a symlink\",\n        ));\n    }\n    let ownership = serde_json::from_slice::<LegacySpilloverOwnership>(&std::fs::read(sidecar)?)\n        .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;\n    if ownership.schema_version != LEGACY_SPILLOVER_OWNER_SCHEMA_VERSION {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            \"unsupported legacy spillover ownership schema\",\n        ));\n    }\n    Ok(ownership)\n}\n\n/// Resolve the spillover-file path for a SHA256 content hash. Separate\n/// namespace (`sha_<hex>.txt`) from the tool-call-id files so legacy\n/// SHA-addressed evidence can be recognized without colliding with\n/// tool-call references. Retrieval still requires matching ownership\n/// metadata. `sha` must be the raw 64-char lowercase hex digest —\n/// case-insensitive matching is done by the caller.\n#[must_use]\npub fn sha_spillover_path(sha: &str) -> Option<PathBuf> {\n    let sha = sha.trim().to_ascii_lowercase();","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tools/truncate.rs#L154-L190","documentation":"read_legacy_spillover_ownership parses the ownership sidecar JSON into LegacySpilloverOwnership. If the bytes are not valid JSON or do not match the struct's schema, serde's error is wrapped as io::ErrorKind::InvalidData. The sidecar passed the symlink check, so this is purely a content problem: corrupt, truncated, or schema-incompatible data.","triggerScenarios":"Calling read_legacy_spillover_ownership when the sidecar exists and is a regular file but its contents fail serde_json::from_slice::<LegacySpilloverOwnership> — missing fields, wrong types, trailing garbage, or empty file.","commonSituations":"Interrupted write of the sidecar (crash before write_atomic completed); manual edits; version skew where the sidecar format changed; an unrelated file substituted at the sidecar path.","solutions":["Inspect the sidecar's JSON content (jq .) and the wrapped serde message to identify the mismatch.","Delete the corrupt sidecar and let the owning session republish ownership via publish_legacy_spillover_ownership.","If format changed between versions, migrate old sidecars or treat them as legacy and regenerate.","Guard callers so InvalidData means 'unowned/corrupt payload' and triggers the legacy-recovery path instead of aborting."],"exampleFix":"// before\nlet ownership = read_legacy_spillover_ownership(&payload_path)?;\n// after\nlet ownership = read_legacy_spillover_ownership(&payload_path)\n    .or_else(|e| if e.kind() == std::io::ErrorKind::InvalidData {\n        republish_ownership(&payload_path) // regenerate sidecar\n    } else { Err(e) })?;","handlingStrategy":"try-catch","validationCode":"fn sidecar_parses(p: &std::path::Path) -> bool {\n    std::fs::read(p).map(|b| serde_json::from_slice::<serde_json::Value>(&b).is_ok()).unwrap_or(false)\n}","typeGuard":"fn parses_as_ownership(raw: &[u8]) -> bool {\n    serde_json::from_slice::<crate::tools::LegacySpilloverOwnership>(raw).is_ok()\n}","tryCatchPattern":"match read_legacy_spillover_ownership(&payload_path) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => republish_ownership(&payload_path),\n    other => other?,\n}","preventionTips":["Write sidecars only via write_atomic so readers never see partial JSON","Do not hand-edit sidecar files","Handle version skew by regenerating sidecars after upgrades"],"tags":["serde","json","deserialization","spillover","io"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T10:30:35.592Z"}