Hmbown/CodeWhale · error · anyhow

Source changed; refresh the import preview

Error message

Source changed; refresh the import preview

What it means

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.

Solutions

  1. Re-run `/mcp import` to refresh the preview and get a token bound to the current hash.
  2. Diff the source config to confirm the change was expected before re-approving.
  3. Apply promptly after previewing to shrink the window for concurrent edits.

Example fix

// before
codewhale mcp import apply <token-with-old-hash>
// after
codewhale mcp import preview   # token now matches current hash
codewhale mcp import apply <new-token>
Defensive patterns

Strategy: retry

Validate before calling

let (candidates, _) = context.discover();
let fresh = candidates.iter().find(|c| candidate_id(c) == id);
let matches = fresh.map(|c| c.content_hash == token_hash).unwrap_or(false);
if !matches { /* refresh preview */ }

Try / catch

match apply_reviewed_import(...) {
    Err(e) if e.to_string().contains("Source changed") => {
        // refresh preview, re-review the new content, retry with the new token
    }
    other => other?,
}

Prevention

When it happens

Trigger: Approving an import after the external source's config file was edited (server URL, command, args, or env changed) since the preview was generated.

Common situations: 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.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/ce772e11ba32587e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/external_import.rs:679

    context: &ImportContext<'_>,
    id: &str,
    hash: &str,
    revision: &str,
    decision: ImportDecision,
) -> anyhow::Result<ImportReceipt> {
    anyhow::ensure!(
        matches!(decision, ImportDecision::Approve | ImportDecision::Decline),
        "Choose approve or decline"
    );
    let (candidate, revision) = super::mutate_config(context.mcp_path, Some(revision), |config| {
        let (candidates, _) = context.discover();
        let candidate = candidates
            .into_iter()
            .find(|candidate| candidate_id(candidate) == id)
            .ok_or_else(|| {
                anyhow::anyhow!("Reviewed source is unavailable; refresh the import preview")
            })?;
        anyhow::ensure!(
            candidate.content_hash == hash,
            "Source changed; refresh the import preview"
        );
        if decision == ImportDecision::Approve {
            anyhow::ensure!(
                !source_blocked(context, &candidate),
                "Source is disabled or its workspace is untrusted"
            );
            anyhow::ensure!(
                !context.merged()?.servers.contains_key(&candidate.name)
                    && !config.servers.contains_key(&candidate.name),
                "A managed, project or plugin connector already uses this name"
            );
            let mut server = candidate.server.clone();
            server.enabled = false;
            server.disabled = true;
            config.servers.insert(candidate.name.clone(), server);
        }

View on GitHub (pinned to 73e0f67d83)