Hmbown/CodeWhale · error · io::Error

invalid approval receipt at line

Error message

invalid approval receipt at line {}: {err}

What it means

While loading approval receipts from the on-disk log (`load_unlocked`, called by `load` and `append`), each line must deserialize into a receipt via serde_json. A line that fails JSON parsing or does not match the receipt schema is wrapped in an InvalidData io::Error reporting the 1-based line number and the serde error.

Solutions

  1. Open the file at the reported line number and fix or remove the malformed line (keep one valid JSON receipt per line).
  2. Back up the log, then delete corrupt trailing/partial lines (typically the last line after a crash).
  3. Check whether a codewhale version changed the receipt schema; regenerate or migrate old logs.
  4. Ensure only one process writes the log at a time (use the provided locked APIs, not raw file edits).

Example fix

// before: manually appended JSON object spanning multiple lines
// after: one compact JSON receipt per line
{"session_id":"abc","tool":"bash","decision":"allow"}
Defensive patterns

Strategy: try-catch

Validate before calling

fn log_lines_valid(path: &Path) -> bool {
    std::fs::read_to_string(path).map(|s| {
        s.lines().enumerate().all(|(i, l)| {
            serde_json::from_str::<serde_json::Value>(l)
                .map_err(|e| { eprintln!("line {}: {e}", i + 1); e })
                .is_ok()
        })
    }).unwrap_or(false)
}

Try / catch

match log.load() {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
        // parse line number from message, truncate/repair log, retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Reading an approval log file where any line is empty, truncated (crash mid-write), hand-edited, or a valid JSON value that doesn't match the Receipt schema (missing/renamed fields).

Common situations: A crash or kill -9 during append leaving a partial line; manual editing of the log; a version change in the Receipt struct making old entries incompatible; log corrupted by two writers without locking.

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/1a1b14a3e2e5f7d0. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/approval_log.rs:248

        match OpenOptions::new().read(true).open(path) {
            Ok(file) => Ok(Some(file)),
            Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
            Err(err) => Err(err),
        }
    }

    fn load_unlocked(&self, session_id: &str) -> io::Result<Vec<ApprovalReceipt>> {
        let path = self.log_path(session_id)?;
        let file = match File::open(path) {
            Ok(file) => file,
            Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
            Err(err) => return Err(err),
        };
        let mut receipts = Vec::new();
        for (index, line) in BufReader::new(file).lines().enumerate() {
            let line = line?;
            let receipt = serde_json::from_str(&line).map_err(|err| {
                io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!("invalid approval receipt at line {}: {err}", index + 1),
                )
            })?;
            receipts.push(receipt);
        }
        ApprovalReplay::from_receipts(&receipts)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
        Ok(receipts)
    }

    pub(crate) fn load(&self, session_id: &str) -> io::Result<Vec<ApprovalReceipt>> {
        if !self.log_path(session_id)?.exists() {
            return Ok(Vec::new());
        }
        let Some(lock_file) = self.open_existing_lock_file(session_id)? else {
            // Imported or legacy snapshots can contain a receipt log without
            // its ephemeral lock file. Preserve read-only session loading;

View on GitHub (pinned to 73e0f67d83)