{"record":{"id":"7a279ddb1e209691","repo":"jdx/mise","slug":"artifact-target-has-changed","errorCode":null,"errorMessage":"artifact target has changed: {}","messagePattern":"artifact target has changed: (.+?)","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":7114,"sourceCode":"    for path in &receipt.completions {\n        let record = records\n            .get(path)\n            .ok_or_else(|| eyre!(\"missing completion target record\"))?;\n        if record.fingerprint.kind != CaskTargetKind::Symlink\n            || !completion_roots\n                .iter()\n                .any(|root| path_is_below(path, root))\n            || !symlink_resolves_below(path, &candidate.version_dir)\n        {\n            bail!(\n                \"completion target is not an owned Caskroom symlink: {}\",\n                path.display()\n            );\n        }\n    }\n    for record in &receipt.targets {\n        if !cask_target_record_matches(record)? {\n            bail!(\"artifact target has changed: {}\", record.path.display());\n        }\n    }\n    Ok(())\n}\n\nfn path_is_below(path: &Path, root: &Path) -> bool {\n    path.strip_prefix(root)\n        .is_ok_and(|relative| relative.components().next().is_some())\n}\n\nfn staged_target_matches(record: &CaskTargetRecord, staged: &Path) -> bool {\n    cask_target_fingerprint(staged).is_ok_and(|fingerprint| fingerprint == record.fingerprint)\n}\n\nfn staged_app_matches_target(record: &CaskTargetRecord, staged: &Path) -> bool {\n    let Ok(metadata) = staged.symlink_metadata() else {\n        return false;\n    };","sourceCodeStart":7096,"sourceCodeEnd":7132,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L7096-L7132","documentation":"Final prune check: every recorded target must still fingerprint identically to install time (sha256 for files, link-target hash for symlinks, directory digest for bundles). Any drift — a modified binary, an app that self-updated, an edited font — means the artifact is no longer what was installed, so the cask is skipped to avoid destroying user changes.","triggerScenarios":"validate_cask_prune_candidate final loop: cask_target_record_matches(record) returns false because cask_target_fingerprint(record.path) errors (path gone) or yields a different digest. Common after apps update themselves in place, users edit installed files, or macOS tooling rewrites metadata inside bundles.","commonSituations":"GUI app ran and rewrote its own bundle contents; user patched a binary or config inside an .app; codesign/notarization quarantine changed a file; artifact deleted before prune.","solutions":["Reinstall the cask to refresh receipt fingerprints against current artifacts, then run prune again","If the changed artifact holds data you care about (app settings inside the bundle), back it up before reinstalling","Remove the drifted artifact manually and uninstall the cask instead of pruning"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Re-fingerprint all targets immediately before apply; drop drifted casks.\nfn targets_unchanged(receipt: &CaskReceipt) -> Result<bool> {\n    receipt.targets.iter().map(cask_target_record_matches)\n        .collect::<Result<Vec<_>>>()\n        .map(|v| v.iter().all(|&ok| ok))\n}","typeGuard":"fn all_fingerprints_match(r: &CaskReceipt) -> bool {\n    r.targets.iter().all(|t| cask_target_record_matches(t).unwrap_or(false))\n}","tryCatchPattern":"// The apply loop pattern: warn + skip so one drifted artifact does not\n// block pruning every other cask.\nif let Err(reason) = validate_cask_prune_candidate(candidate) {\n    warn!(\"brew-cask:{}: skipped because recorded artifacts changed after planning: {reason:#}\", candidate.token);\n    continue;\n}","preventionTips":["Expect self-updating apps to invalidate receipts; reinstall before pruning them","Back up data living inside artifact bundles before any reinstall that refreshes fingerprints","Run prune soon after uninstalling the owning package, before artifacts drift"],"tags":["brew","cask","prune","fingerprint","integrity"],"backgroundTag":"file-checksum-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}