jdx/mise · error
ownership receipt has changed
Error message
ownership receipt has changed
What it means
validate_cask_prune_candidate compares the on-disk ownership receipt with the receipt captured in the prune candidate. If they differ, the cask's state changed since planning (reinstall, version bump, metadata rewrite), so pruning from the stale plan is unsafe and mise bails. This is the TOCTOU guard on the receipt itself.
Source
Thrown at src/system/packages/brew/cask/state.rs:1036
.get(&target.path)
.is_some_and(|tokens| tokens.iter().any(|token| token != &candidate.token))
{
bail!(
"artifact target is now claimed by another cask: {}",
target.path.display()
);
}
}
Ok(())
}
pub(super) fn validate_cask_prune_candidate(candidate: &CaskPruneCandidate) -> Result<()> {
if homebrew_metadata_present(&candidate.token)? {
bail!("Homebrew now owns this cask");
}
let receipt = &candidate.receipt;
if read_receipt(&candidate.version_dir)?.as_ref() != Some(receipt) {
bail!("ownership receipt has changed");
}
if receipt.schema_version != 3 || !receipt.prune_safe || !receipt.pkg_ids.is_empty() {
bail!("receipt is not marked safe for direct-artifact pruning");
}
if !receipt.metadata_only_apps.is_empty() {
bail!("metadata-only app ownership cannot be proven safely during pruning");
}
let records = receipt
.targets
.iter()
.map(|record| (record.path.clone(), record))
.collect::<BTreeMap<_, _>>();
let expected = receipt.standard_targets().cloned().collect::<BTreeSet<_>>();
if expected.is_empty()
|| records.len() != receipt.targets.len()
|| records.len() != expected.len()
|| receipt
.metadata_only_appsView on GitHub (pinned to afd2eddd3a)
Solutions
- Re-run the prune to rebuild the plan from the current receipt
- If the change was unintended, reinstall the cask via mise to restore a known-good receipt
- Avoid mutating the cask (mise or brew operations) between planning and pruning
Example fix
// before: plan made, then cask reinstalled, then apply mise cask prune <token> # "ownership receipt has changed" // after: regenerate plan against current state mise cask prune <token> # freshly planned, receipts match
Defensive patterns
Strategy: retry
Validate before calling
// ensure the cask was not mutated after planning // compare current receipt digest with the planned one before applying assert_receipt_unchanged(&candidate.version_dir, &candidate.receipt)?;
Try / catch
if let Err(e) = prune(plan) {
if e.to_string().contains("ownership receipt has changed") {
let fresh_plan = build_fresh_plan(token)?;
return prune(fresh_plan);
}
return Err(e);
} Prevention
- Apply prune plans immediately; don't cache them across operations
- Never mutate (reinstall/upgrade) a cask between planning and pruning
- Re-plan after any Homebrew or mise package operation touching the token
When it happens
Trigger: read_receipt(&candidate.version_dir) returns a receipt that does not compare equal (schema version, targets, pkg_ids, prune_safe flag, etc.) to candidate.receipt, during cask_prune_plan_from_tokens or apply_cask_prune_plan_in.
Common situations: Cask was reinstalled/upgraded between planning and applying the prune; another process rewrote the mise receipt; plan was made long before apply and the cask changed in between.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ownership receipt has changed
- receipt is not marked safe for direct-artifact pruning
- metadata-only app ownership cannot be proven safely during p
- receipt target inventory is incomplete or duplicated
- receipt target inventory contains an unclassified path
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/53e520a1a3d35304.
Report an issue: GitHub.