{"record":{"id":"ce772e11ba32587e","repo":"Hmbown/CodeWhale","slug":"source-changed-refresh-the-import-preview","errorCode":null,"errorMessage":"Source changed; refresh the import preview","messagePattern":"Source changed; refresh the import preview","errorType":"validation","errorClass":"anyhow","httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp/external_import.rs","lineNumber":679,"sourceCode":"    context: &ImportContext<'_>,\n    id: &str,\n    hash: &str,\n    revision: &str,\n    decision: ImportDecision,\n) -> anyhow::Result<ImportReceipt> {\n    anyhow::ensure!(\n        matches!(decision, ImportDecision::Approve | ImportDecision::Decline),\n        \"Choose approve or decline\"\n    );\n    let (candidate, revision) = super::mutate_config(context.mcp_path, Some(revision), |config| {\n        let (candidates, _) = context.discover();\n        let candidate = candidates\n            .into_iter()\n            .find(|candidate| candidate_id(candidate) == id)\n            .ok_or_else(|| {\n                anyhow::anyhow!(\"Reviewed source is unavailable; refresh the import preview\")\n            })?;\n        anyhow::ensure!(\n            candidate.content_hash == hash,\n            \"Source changed; refresh the import preview\"\n        );\n        if decision == ImportDecision::Approve {\n            anyhow::ensure!(\n                !source_blocked(context, &candidate),\n                \"Source is disabled or its workspace is untrusted\"\n            );\n            anyhow::ensure!(\n                !context.merged()?.servers.contains_key(&candidate.name)\n                    && !config.servers.contains_key(&candidate.name),\n                \"A managed, project or plugin connector already uses this name\"\n            );\n            let mut server = candidate.server.clone();\n            server.enabled = false;\n            server.disabled = true;\n            config.servers.insert(candidate.name.clone(), server);\n        }","sourceCodeStart":661,"sourceCodeEnd":697,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp/external_import.rs#L661-L697","documentation":"The reviewed candidate was found by id, but its freshly re-computed `content_hash` no longer matches the hash embedded in the review token. The source file changed between preview and apply, so the earlier approval no longer describes what would be imported.","triggerScenarios":"Approving an import after the external source's config file was edited (server URL, command, args, or env changed) since the preview was generated.","commonSituations":"The source application rewrote its own config (updated credentials, added fields); another sync tool (dotfile manager) touched the file between preview and apply; the user edited it manually.","solutions":["Re-run `/mcp import` to refresh the preview and get a token bound to the current hash.","Diff the source config to confirm the change was expected before re-approving.","Apply promptly after previewing to shrink the window for concurrent edits."],"exampleFix":"// before\ncodewhale mcp import apply <token-with-old-hash>\n// after\ncodewhale mcp import preview   # token now matches current hash\ncodewhale mcp import apply <new-token>","handlingStrategy":"retry","validationCode":"let (candidates, _) = context.discover();\nlet fresh = candidates.iter().find(|c| candidate_id(c) == id);\nlet matches = fresh.map(|c| c.content_hash == token_hash).unwrap_or(false);\nif !matches { /* refresh preview */ }","typeGuard":null,"tryCatchPattern":"match apply_reviewed_import(...) {\n    Err(e) if e.to_string().contains(\"Source changed\") => {\n        // refresh preview, re-review the new content, retry with the new token\n    }\n    other => other?,\n}","preventionTips":["Avoid editing the source config between preview and apply","Apply tokens promptly","Re-run /mcp import after any tool that rewrites external configs"],"tags":["stale-state","checksum","import"],"backgroundTag":"checksum-mismatch","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"}