{"record":{"id":"1a1b14a3e2e5f7d0","repo":"Hmbown/CodeWhale","slug":"invalid-approval-receipt-at-line-err","errorCode":null,"errorMessage":"invalid approval receipt at line {}: {err}","messagePattern":"invalid approval receipt at line (.+?): (.+?)","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/approval_log.rs","lineNumber":248,"sourceCode":"        match OpenOptions::new().read(true).open(path) {\n            Ok(file) => Ok(Some(file)),\n            Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),\n            Err(err) => Err(err),\n        }\n    }\n\n    fn load_unlocked(&self, session_id: &str) -> io::Result<Vec<ApprovalReceipt>> {\n        let path = self.log_path(session_id)?;\n        let file = match File::open(path) {\n            Ok(file) => file,\n            Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),\n            Err(err) => return Err(err),\n        };\n        let mut receipts = Vec::new();\n        for (index, line) in BufReader::new(file).lines().enumerate() {\n            let line = line?;\n            let receipt = serde_json::from_str(&line).map_err(|err| {\n                io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\"invalid approval receipt at line {}: {err}\", index + 1),\n                )\n            })?;\n            receipts.push(receipt);\n        }\n        ApprovalReplay::from_receipts(&receipts)\n            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;\n        Ok(receipts)\n    }\n\n    pub(crate) fn load(&self, session_id: &str) -> io::Result<Vec<ApprovalReceipt>> {\n        if !self.log_path(session_id)?.exists() {\n            return Ok(Vec::new());\n        }\n        let Some(lock_file) = self.open_existing_lock_file(session_id)? else {\n            // Imported or legacy snapshots can contain a receipt log without\n            // its ephemeral lock file. Preserve read-only session loading;","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/approval_log.rs#L230-L266","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Open the file at the reported line number and fix or remove the malformed line (keep one valid JSON receipt per line).","Back up the log, then delete corrupt trailing/partial lines (typically the last line after a crash).","Check whether a codewhale version changed the receipt schema; regenerate or migrate old logs.","Ensure only one process writes the log at a time (use the provided locked APIs, not raw file edits)."],"exampleFix":"// before: manually appended JSON object spanning multiple lines\n// after: one compact JSON receipt per line\n{\"session_id\":\"abc\",\"tool\":\"bash\",\"decision\":\"allow\"}","handlingStrategy":"try-catch","validationCode":"fn log_lines_valid(path: &Path) -> bool {\n    std::fs::read_to_string(path).map(|s| {\n        s.lines().enumerate().all(|(i, l)| {\n            serde_json::from_str::<serde_json::Value>(l)\n                .map_err(|e| { eprintln!(\"line {}: {e}\", i + 1); e })\n                .is_ok()\n        })\n    }).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match log.load() {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {\n        // parse line number from message, truncate/repair log, retry\n    }\n    other => other?,\n}","preventionTips":["Never hand-edit approval logs; use the locked API.","Write one compact JSON receipt per line.","Handle partial last lines after crashes before loading.","Migrate logs when the Receipt schema changes between versions."],"tags":["json","persistence","corruption","serde"],"backgroundTag":"json-parse-error","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}