jdx/mise · error · eyre::Report
artifact target has changed: {}
Error message
artifact target has changed: {} What it means
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.
Source
Thrown at src/system/packages/brew/cask.rs:7114
for path in &receipt.completions {
let record = records
.get(path)
.ok_or_else(|| eyre!("missing completion target record"))?;
if record.fingerprint.kind != CaskTargetKind::Symlink
|| !completion_roots
.iter()
.any(|root| path_is_below(path, root))
|| !symlink_resolves_below(path, &candidate.version_dir)
{
bail!(
"completion target is not an owned Caskroom symlink: {}",
path.display()
);
}
}
for record in &receipt.targets {
if !cask_target_record_matches(record)? {
bail!("artifact target has changed: {}", record.path.display());
}
}
Ok(())
}
fn path_is_below(path: &Path, root: &Path) -> bool {
path.strip_prefix(root)
.is_ok_and(|relative| relative.components().next().is_some())
}
fn staged_target_matches(record: &CaskTargetRecord, staged: &Path) -> bool {
cask_target_fingerprint(staged).is_ok_and(|fingerprint| fingerprint == record.fingerprint)
}
fn staged_app_matches_target(record: &CaskTargetRecord, staged: &Path) -> bool {
let Ok(metadata) = staged.symlink_metadata() else {
return false;
};View on GitHub (pinned to 9dcfcaa0dc)
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
Defensive patterns
Strategy: validation
Validate before calling
// Re-fingerprint all targets immediately before apply; drop drifted casks.
fn targets_unchanged(receipt: &CaskReceipt) -> Result<bool> {
receipt.targets.iter().map(cask_target_record_matches)
.collect::<Result<Vec<_>>>()
.map(|v| v.iter().all(|&ok| ok))
} Type guard
fn all_fingerprints_match(r: &CaskReceipt) -> bool {
r.targets.iter().all(|t| cask_target_record_matches(t).unwrap_or(false))
} Try / catch
// The apply loop pattern: warn + skip so one drifted artifact does not
// block pruning every other cask.
if let Err(reason) = validate_cask_prune_candidate(candidate) {
warn!("brew-cask:{}: skipped because recorded artifacts changed after planning: {reason:#}", candidate.token);
continue;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- binary target is not an owned Caskroom symlink: {}
- completion target is not an owned Caskroom symlink: {}
- ownership receipt has changed
- receipt is not marked safe for direct-artifact pruning
- receipt target inventory is incomplete or duplicated
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/7a279ddb1e209691.
Report an issue: GitHub.