{"record":{"id":"27af2ad621257e31","repo":"jdx/mise","slug":"receipt-target-inventory-is-incomplete-or-duplicat-27af2a","errorCode":null,"errorMessage":"receipt target inventory is incomplete or duplicated","messagePattern":"receipt target inventory is incomplete or duplicated","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/state.rs","lineNumber":1058,"sourceCode":"    }\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\n            .iter()\n            .any(|path| !receipt.apps.contains(path))\n    {\n        bail!(\"receipt target inventory is incomplete or duplicated\");\n    }\n    if records.keys().any(|path| !expected.contains(path)) {\n        bail!(\"receipt target inventory contains an unclassified path\");\n    }\n\n    for path in &receipt.apps {\n        let record = records\n            .get(path)\n            .ok_or_else(|| eyre!(\"missing app target record\"))?;\n        if record.fingerprint.kind != CaskTargetKind::Directory\n            || !allowed_appdir_roots()?\n                .iter()\n                .any(|root| path_is_below(path, root))\n            || !path.file_name().is_some_and(|name| {\n                staged_app_matches_target(record, &candidate.version_dir.join(name))\n            })\n        {\n            bail!(","sourceCodeStart":1040,"sourceCodeEnd":1076,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/state.rs#L1040-L1076","documentation":"mise's brew-cask direct-artifact pruning validates the ownership receipt before deleting a staged cask version. This error means the receipt's target inventory failed structural consistency checks: there are no standard targets, the targets list contains duplicate paths, or a metadata-only app path is not also listed in the apps list. The library refuses to prune because the receipt no longer faithfully describes the staged artifact, making a safe deletion impossible to prove.","triggerScenarios":"Calling cask_prune_plan_from_tokens or apply_cask_prune_plan_in with a candidate whose receipt has an empty standard_targets() set, duplicate target paths (records.len() != receipt.targets.len() or records.len() != expected.len()), or a metadata_only_apps entry missing from receipt.apps.","commonSituations":"Hand-edited or corrupted receipt JSON in the Caskroom version directory; a receipt written by an older mise version with a different inventory layout; a cask whose only targets are metadata-only apps so standard_targets() is empty; concurrent writes leaving a partially updated receipt.","solutions":["Delete the stale version directory (and receipt) and reinstall the cask via mise so a fresh, consistent receipt is written","Inspect the receipt JSON in ~/.../Caskroom/<token>/<version>/.mise-receipt (or equivalent) for duplicate target paths or an empty target list and fix or regenerate it","Upgrade mise — older receipts may use a schema the current validator rejects; ensure schema_version 3 receipts are produced","Skip pruning that cask (remove its token from the prune list); Homebrew-managed or metadata-only casks should be pruned via brew itself"],"exampleFix":"// before: hand-edited receipt with duplicate target\n{\"targets\": [{\"path\": \"/Applications/Foo.app\"}, {\"path\": \"/Applications/Foo.app\"}], ...}\n// after: reinstall to regenerate a clean receipt\nmise uninstall brew-cask:foo && mise install brew-cask:foo","handlingStrategy":"validation","validationCode":"// before planning a prune, sanity-check the receipt inventory\nfn receipt_inventory_ok(receipt: &CaskReceipt) -> bool {\n    let records: BTreeSet<_> = receipt.targets.iter().map(|t| &t.path).collect();\n    let expected: BTreeSet<_> = receipt.standard_targets().collect();\n    !expected.is_empty()\n        && records.len() == receipt.targets.len()\n        && records == expected\n        && receipt.metadata_only_apps.iter().all(|p| receipt.apps.contains(p))\n}\nif !receipt_inventory_ok(&candidate.receipt) { /* skip or reinstall cask */ }","typeGuard":"fn has_complete_inventory(receipt: &CaskReceipt) -> bool {\n    !receipt.standard_targets().next().is_none()\n        && receipt.targets.len() == receipt.targets.iter().map(|t| &t.path).collect::<BTreeSet<_>>().len()\n        && receipt.metadata_only_apps.iter().all(|p| receipt.apps.contains(p))\n}","tryCatchPattern":"match apply_cask_prune_plan_in(&plan) {\n    Ok(removed) => info!(\"pruned {removed} casks\"),\n    Err(e) if e.to_string().contains(\"inventory is incomplete or duplicated\") => {\n        warn!(\"skipping cask with malformed receipt: {e:#}\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never hand-edit cask ownership receipt files in the Caskroom","Regenerate receipts by reinstalling the cask after failed or interrupted operations","Keep mise updated so receipt schema_version stays at the supported value","Validate receipts (standard_targets non-empty, no duplicate paths) before adding tokens to a prune plan"],"tags":["brew","cask","validation","receipt"],"backgroundTag":"schema-validation-failed","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}