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

  1. Read the wrapped serde error to identify the exact offset/field that failed to parse.
  2. Delete the corrupt sidecar and let publish_evidence_metadata regenerate it from the artifact.
  3. If the file predates a schema change, migrate or regenerate it with the version that wrote it.
  4. 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

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.

Related errors


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)