{"record":{"id":"53e520a1a3d35304","repo":"jdx/mise","slug":"ownership-receipt-has-changed-53e520","errorCode":null,"errorMessage":"ownership receipt has changed","messagePattern":"ownership receipt has changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/state.rs","lineNumber":1036,"sourceCode":"            .get(&target.path)\n            .is_some_and(|tokens| tokens.iter().any(|token| token != &candidate.token))\n        {\n            bail!(\n                \"artifact target is now claimed by another cask: {}\",\n                target.path.display()\n            );\n        }\n    }\n    Ok(())\n}\n\npub(super) fn validate_cask_prune_candidate(candidate: &CaskPruneCandidate) -> Result<()> {\n    if homebrew_metadata_present(&candidate.token)? {\n        bail!(\"Homebrew now owns this cask\");\n    }\n    let receipt = &candidate.receipt;\n    if read_receipt(&candidate.version_dir)?.as_ref() != Some(receipt) {\n        bail!(\"ownership receipt has changed\");\n    }\n    if receipt.schema_version != 3 || !receipt.prune_safe || !receipt.pkg_ids.is_empty() {\n        bail!(\"receipt is not marked safe for direct-artifact pruning\");\n    }\n    if !receipt.metadata_only_apps.is_empty() {\n        bail!(\"metadata-only app ownership cannot be proven safely during pruning\");\n    }\n    let records = receipt\n        .targets\n        .iter()\n        .map(|record| (record.path.clone(), record))\n        .collect::<BTreeMap<_, _>>();\n    let expected = receipt.standard_targets().cloned().collect::<BTreeSet<_>>();\n    if expected.is_empty()\n        || records.len() != receipt.targets.len()\n        || records.len() != expected.len()\n        || receipt\n            .metadata_only_apps","sourceCodeStart":1018,"sourceCodeEnd":1054,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/state.rs#L1018-L1054","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: plan made, then cask reinstalled, then apply\nmise cask prune <token>   # \"ownership receipt has changed\"\n// after: regenerate plan against current state\nmise cask prune <token>  # freshly planned, receipts match","handlingStrategy":"retry","validationCode":"// ensure the cask was not mutated after planning\n// compare current receipt digest with the planned one before applying\nassert_receipt_unchanged(&candidate.version_dir, &candidate.receipt)?;","typeGuard":null,"tryCatchPattern":"if let Err(e) = prune(plan) {\n    if e.to_string().contains(\"ownership receipt has changed\") {\n        let fresh_plan = build_fresh_plan(token)?;\n        return prune(fresh_plan);\n    }\n    return Err(e);\n}","preventionTips":["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"],"tags":["cask","receipt","prune","stale-state","toctou"],"backgroundTag":"invalid-state-transition","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}