{"record":{"id":"41706689894cd659","repo":"jdx/mise","slug":"receipt-is-not-marked-safe-for-direct-artifact-pru","errorCode":null,"errorMessage":"receipt is not marked safe for direct-artifact pruning","messagePattern":"receipt is not marked safe for direct-artifact pruning","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":7013,"sourceCode":"            bail!(\n                \"artifact target is now claimed by another cask: {}\",\n                target.path.display()\n            );\n        }\n    }\n    Ok(())\n}\n\nfn 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    let records = receipt\n        .targets\n        .iter()\n        .map(|record| (record.path.clone(), record))\n        .collect::<BTreeMap<_, _>>();\n    let expected = receipt\n        .apps\n        .iter()\n        .chain(&receipt.binaries)\n        .chain(&receipt.fonts)\n        .chain(&receipt.completions)\n        .cloned()\n        .collect::<BTreeSet<_>>();\n    if expected.is_empty()\n        || records.len() != receipt.targets.len()\n        || records.len() != expected.len()\n    {","sourceCodeStart":6995,"sourceCodeEnd":7031,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L6995-L7031","documentation":"The direct-artifact prune path only deletes artifacts for receipts it fully understands: schema_version must be 3, prune_safe must be true, and pkg_ids must be empty. Receipts written by older versions (schema 1/2), receipts with a prune_blocker (casks with uninstall scripts, suite/pkg behavior), and casks that installed macOS installer packages are refused because removal requires logic beyond deleting recorded files and symlinks.","triggerScenarios":"validate_cask_prune_candidate after the receipt equality check: receipt.schema_version != 3, !receipt.prune_safe, or receipt.pkg_ids non-empty. Reachable when a hand-crafted candidate skips the plan-phase pre-checks (cask.rs:6832/6840 normally skip these with friendlier reasons), e.g. calling apply with a plan from a mixed-version install.","commonSituations":"Cask installed by an older mise whose receipts predate prune metadata; cask definition includes pkg installer artifacts (e.g. drivers, system tools); cask has uninstall lifecycle actions that mise refuses to run; upgrading mise but not reinstalling the cask.","solutions":["Reinstall the affected cask with the current mise version so write_receipt_with_flight_targets emits a schema-3 receipt (schema_version: 3, prune_safe set honestly)","Check the receipt's prune_blocker field in Caskroom/<token>/<version>/.mise-cask.toml — if set, uninstall via 'mise brew uninstall' instead of pruning","For pkg-based casks (pkg_ids non-empty), remove the packages via macOS (pkgutil) or keep them managed by Homebrew proper"],"exampleFix":"# before: receipt shows schema 1 / prune blocker\ncat \"$(mise brew prefix)/Caskroom/some-cask/1.0/.mise-cask.toml\"\n# schema_version = 1\n\n# after: reinstall to regenerate a current receipt\nmise brew install --force some-cask\nmise system prune","handlingStrategy":"validation","validationCode":"// Only offer a receipt to the prune path when it is schema 3, prune-safe,\n// and pkg-free — mirror the checks before planning.\nfn prune_eligible(receipt: &CaskReceipt) -> bool {\n    receipt.schema_version == 3 && receipt.prune_safe && receipt.pkg_ids.is_empty()\n}","typeGuard":"fn is_schema3_prune_safe(r: &CaskReceipt) -> bool {\n    r.schema_version == 3 && r.prune_safe && r.pkg_ids.is_empty()\n}","tryCatchPattern":"// Plan-phase pattern: pre-check and record a friendly skip reason instead of\n// letting the hard bail surface during apply.\nif receipt.schema_version != 3 || !receipt.prune_safe || !receipt.pkg_ids.is_empty() {\n    plan.skipped.push(CaskPruneSkip { token, reason: \"receipt predates safe prune metadata; upgrade or reinstall first\".into() });\n    continue;\n}","preventionTips":["Reinstall casks after upgrading mise so receipts carry schema 3 + prune metadata","Prefer explicit 'mise brew uninstall' for pkg-based or scripted casks; pruning is only for direct-artifact casks","Check receipt.prune_blocker before expecting a prune to proceed"],"tags":["brew","cask","prune","receipt","schema-version"],"backgroundTag":"schema-version-mismatch","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}