{"record":{"id":"2a24d4d31df7649d","repo":"Hmbown/CodeWhale","slug":"invalid-mcp-consent-history-contents-omitted","errorCode":null,"errorMessage":"Invalid MCP consent history; contents omitted","messagePattern":"Invalid MCP consent history; contents omitted","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/external_import.rs","lineNumber":297,"sourceCode":"fn hex_sha256(bytes: &[u8]) -> String {\n    let digest = Sha256::digest(bytes);\n    digest.iter().map(|b| format!(\"{b:02x}\")).collect()\n}\n\n/// Record decisions against the latest consent document under the shared\n/// process lock. Malformed history is never silently replaced with empty state.\npub fn persist_decisions(\n    path: &Path,\n    candidates: &[ImportCandidate],\n    decisions: &HashMap<String, ImportDecision>,\n    now_unix: u64,\n) -> anyhow::Result<()> {\n    super::validate_mcp_config_path(path)?;\n    codewhale_config::with_config_write_lock(path, |path| {\n        let original = super::read_mcp_config_file(path)?;\n        let mut raw: Value = match original.as_deref() {\n            Some(raw) => serde_json::from_str(raw)\n                .map_err(|_| anyhow::anyhow!(\"Invalid MCP consent history; contents omitted\"))?,\n            None => serde_json::json!({}),\n        };\n        anyhow::ensure!(raw.is_object(), \"MCP consent history must be an object\");\n        let mut store: ImportConsentStore = if original.is_none() {\n            ImportConsentStore::default()\n        } else {\n            serde_json::from_value(raw.clone())\n                .map_err(|_| anyhow::anyhow!(\"Invalid MCP consent history; contents omitted\"))?\n        };\n        let before = serde_json::to_value(&store)?;\n        record_decisions(&mut store, candidates, decisions, now_unix);\n        let after = serde_json::to_value(&store)?;\n        super::apply_json_delta(&mut raw, &before, &after);\n        let rendered = serde_json::to_vec_pretty(&raw)?;\n        if rendered.len() as u64 > super::MAX_MCP_CONFIG_BYTES {\n            anyhow::bail!(\"MCP consent history exceeds size limit\");\n        }\n        crate::utils::write_atomic(path, &rendered)?;","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/external_import.rs#L279-L315","documentation":"persist_decisions writes import-consent decisions into the MCP config file under a process-wide write lock. Before mutating, it re-parses the existing file; if the file exists but is not valid JSON, the write aborts rather than silently replacing the user's malformed consent history with empty state — contents are deliberately not echoed.","triggerScenarios":"`persist_decisions` (via apply_reviewed_import or the consent-transaction test) is called on a path whose config file exists but contains corrupted/partial JSON — a truncated write, manual edit error, or leftover merge-conflict markers.","commonSituations":"A crashed earlier write left a half-written file; a git merge of the config left `<<<<<<<` markers; the file was saved with a non-JSON encoding or stray BOM.","solutions":["Open the config file, fix or remove the invalid JSON, and retry the import flow","If the history is unrecoverable, back up the file, delete it, and let the tool recreate it (prior consent decisions are lost)","Restore the file from version control or backup before re-running the consent flow"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const raw = fs.readFileSync(path, \"utf8\");\ntry { JSON.parse(raw); } catch (e) { /* repair or restore the file before proceeding */ }","typeGuard":null,"tryCatchPattern":"catch the error, then attempt JSON.parse yourself to locate the corruption; repair or restore from backup before retrying the consent flow","preventionTips":["Let the tool perform all writes to the consent/config file (atomic writes); never hand-edit mid-flow","Keep the config under version control so corrupted files can be restored","Watch for merge-conflict markers in config files after git merges"],"tags":["mcp","json","file-corruption","consent"],"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"}