{"record":{"id":"c09acba7d5a4aeef","repo":"Hmbown/CodeWhale","slug":"mcp-consent-history-exceeds-size-limit","errorCode":null,"errorMessage":"MCP consent history exceeds size limit","messagePattern":"MCP consent history exceeds size limit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/external_import.rs","lineNumber":313,"sourceCode":"        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)?;\n        Ok(())\n    })\n}\n\n/// Filter candidates that still need a user decision for this content hash.\n#[allow(dead_code)] // used by future selector UI + unit tests\npub fn candidates_needing_consent(\n    candidates: &[ImportCandidate],\n    store: &ImportConsentStore,\n) -> Vec<ImportCandidate> {\n    candidates\n        .iter()\n        .filter(|c| {\n            let key = consent_key(&c.source_path, &c.content_hash);\n            match store.entries.get(&key) {\n                Some(entry) if entry.decision == ImportDecision::Decline => false,","sourceCodeStart":295,"sourceCodeEnd":331,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/external_import.rs#L295-L331","documentation":"MCP consent decisions are persisted into the MCP config JSON, which shares the 1 MiB size cap (`MAX_MCP_CONFIG_BYTES`). After recording decisions and computing a JSON delta against the previous store, `persist_decisions` re-serializes the file and bails if the rendered output would exceed the limit, preventing unbounded consent-history growth.","triggerScenarios":"Calling `persist_decisions` (via `apply_reviewed_import`) when the resulting pretty-printed consent/config JSON exceeds 1,048,576 bytes (crates/tui/src/mcp/external_import.rs:313) — typically after recording many decisions on an already-large config.","commonSituations":"Importing plugins with very long server name/URL lists repeatedly, accumulating decision records; a consent store already near the cap plus one large import; long-lived config where every import appends history.","solutions":["Trim the existing config: remove stale servers and obsolete consent history entries before re-running the import","Reduce the size of the import batch (fewer, smaller candidate lists)","Start from a fresh/minimal mcp.json if the consent history has accumulated beyond usefulness","Compact env/arg data in the shared file so consent history has room"],"exampleFix":"// before\nmanual: keep importing batches into a 950 KB mcp.json\n// after\nprune unused servers first so rendered size stays under 1 MiB, then re-run apply_reviewed_import","handlingStrategy":"validation","validationCode":"const MAX_MCP_CONFIG_BYTES: u64 = 1_048_576;\nlet size = std::fs::metadata(path)?.len();\nif size + estimated_new_decisions > MAX_MCP_CONFIG_BYTES {\n    eprintln!(\"prune consent history before importing\");\n}","typeGuard":null,"tryCatchPattern":"match persist_decisions(&path, &candidates, &decisions, now) {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"size limit\") => {\n        prune_stale_decisions(&path)?;\n        persist_decisions(&path, &candidates, &decisions, now)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Import in small batches on configs already near the cap","Periodically prune obsolete consent records","Keep shared mcp.json lean so consent history has headroom"],"tags":["mcp","config","limit","consent"],"backgroundTag":"file-size-limit-exceeded","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"}