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

  1. Reinstall the cask to refresh receipt fingerprints against current artifacts, then run prune again
  2. If the changed artifact holds data you care about (app settings inside the bundle), back it up before reinstalling
  3. 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

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


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/7a279ddb1e209691. Report an issue: GitHub.