jdx/mise · error · eyre::Report
ownership receipt has changed
Error message
ownership receipt has changed
What it means
Thrown while pruning a mise-managed Homebrew cask: apply_cask_prune_plan re-reads the cask's ownership receipt (.mise-cask.toml inside the Caskroom version directory) and compares it to the snapshot taken when the prune plan was built. A mismatch means the cask's ownership changed between planning and execution, so pruning is refused rather than risk deleting artifacts that now belong to a different install.
Source
Thrown at src/system/packages/brew/cask.rs:7010
.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(())
}
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");
}
let records = receipt
.targets
.iter()
.map(|record| (record.path.clone(), record))
.collect::<BTreeMap<_, _>>();
let expected = receipt
.apps
.iter()
.chain(&receipt.binaries)
.chain(&receipt.fonts)
.chain(&receipt.completions)
.cloned()
.collect::<BTreeSet<_>>();
if expected.is_empty()View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Re-run the prune so it replans from the receipts currently on disk (plans are validated against live state, stale plans are expected to be discarded)
- Make sure no other mise brew install/upgrade/uninstall or Homebrew command is running against the same prefix while pruning
- Inspect Caskroom/<token>/<version>/.mise-cask.toml — if it is corrupt or foreign, reinstall the cask to regenerate a schema-3 receipt
Example fix
// before: pruning from a plan built earlier, while another mise process ran let plan = cask_prune_plan(...)?; // ... other mise brew install runs here, rewriting receipts ... apply_cask_prune_plan(&plan, false)?; // "ownership receipt has changed" // after: plan and apply in one session, no concurrent brew/mise operations let plan = cask_prune_plan(...)?; apply_cask_prune_plan(&plan, false)?;
Defensive patterns
Strategy: validation
Validate before calling
// Re-read the receipt immediately before applying a prune plan and drop
// candidates whose receipt no longer matches the plan snapshot.
fn still_valid(candidate: &CaskPruneCandidate) -> bool {
read_receipt(&candidate.version_dir)
.ok()
.flatten()
.is_some_and(|current| current == candidate.receipt)
}
let plan_remove: Vec<_> = plan.remove.into_iter().filter(still_valid).collect(); Type guard
fn receipt_unchanged(c: &CaskPruneCandidate) -> bool {
matches!(read_receipt(&c.version_dir), Ok(Some(ref r)) if r == &c.receipt)
} Try / catch
if let Err(reason) = validate_cask_prune_candidate(&candidate) {
// do not abort the whole prune; skip this cask and continue,
// surfacing the reason to the user (same pattern as apply_cask_prune_plan)
warn!("brew-cask:{}: skipped: {reason:#}", candidate.token);
continue;
} Prevention
- Build and apply prune plans in the same invocation; never persist plans across sessions
- Serialize mise brew operations per prefix — the caskroom lock only covers apply, not plan
- Treat receipt churn as expected under concurrency: re-plan instead of retrying the stale plan
When it happens
Trigger: apply_cask_prune_plan -> validate_cask_prune_candidate: read_receipt(version_dir) returns a receipt not equal (PartialEq) to candidate.receipt. Happens when a concurrent mise brew install/upgrade rewrites the receipt after prune_plan ran, when Homebrew takes over the cask and metadata changes, or when .mise-cask.toml is edited/deleted between plan and apply.
Common situations: Two mise (or mise + brew) processes racing on the same Caskroom; running prune from a stale plan file; an interrupted earlier transaction leaving a half-written receipt; hand-editing receipts.
Related errors
- receipt is not marked safe for direct-artifact pruning
- receipt target inventory is incomplete or duplicated
- receipt target inventory contains an unclassified path
- app target is outside an allowed Applications directory: {}
- binary target is not an owned Caskroom symlink: {}
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/1eecc5b4e97b53a5.
Report an issue: GitHub.