{"record":{"id":"bcce9541b7eb9bb6","repo":"jdx/mise","slug":"receipt-target-inventory-is-incomplete-or-duplicat","errorCode":null,"errorMessage":"receipt target inventory is incomplete or duplicated","messagePattern":"receipt target inventory is incomplete or duplicated","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":7032,"sourceCode":"    }\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    {\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":7014,"sourceCodeEnd":7050,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L7014-L7050","documentation":"Before pruning, the receipt's targets list is cross-checked against the union of its classified artifact lists (apps + binaries + fonts + completions). The error fires when that expected set is empty, when the path->record map is smaller than targets (duplicate paths collapsed by the BTreeMap), or when the counts disagree — i.e. the receipt is internally inconsistent and cannot be trusted to describe what is safe to delete.","triggerScenarios":"validate_cask_prune_candidate: expected.is_empty() || records.len() != receipt.targets.len() || records.len() != expected.len(). Produced by a .mise-cask.toml with the same path listed twice in targets, targets missing entries that apps/binaries/fonts/completions reference (or vice versa), or an all-empty receipt.","commonSituations":"Hand-edited or merge-conflicted receipt file; receipt truncated by a crash mid-write; a bug or incompatible version writing inconsistent receipts; disk corruption.","solutions":["Read Caskroom/<token>/<version>/.mise-cask.toml and verify every path in [[targets]] also appears in apps/binaries/fonts/completions exactly once with no duplicates","Reinstall the cask to let write_receipt_with_flight_targets regenerate a consistent receipt","If the file is corrupted beyond repair, uninstall and reinstall the cask, or delete the version directory manually after verifying no symlinks point into it"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Verify a receipt is internally consistent before handing it to prune.\nuse std::collections::{BTreeMap, BTreeSet};\nfn inventory_consistent(r: &CaskReceipt) -> bool {\n    let records: BTreeMap<&Path, _> = r.targets.iter().map(|t| (t.path.as_path(), t)).collect();\n    let expected: BTreeSet<&Path> = r.apps.iter()\n        .chain(&r.binaries).chain(&r.fonts).chain(&r.completions)\n        .map(|p| p.as_path()).collect();\n    !expected.is_empty()\n        && records.len() == r.targets.len()\n        && records.len() == expected.len()\n}","typeGuard":"fn receipt_inventory_ok(r: &CaskReceipt) -> bool {\n    let dup = r.targets.len() != r.targets.iter().map(|t| &t.path).collect::<std::collections::BTreeSet<_>>().len();\n    !dup && !r.apps.is_empty() || !r.binaries.is_empty() || !r.fonts.is_empty() || !r.completions.is_empty()\n}","tryCatchPattern":"if let Err(reason) = validate_cask_prune_candidate(&candidate) {\n    plan.skipped.push(CaskPruneSkip { token, reason: format!(\"recorded artifacts cannot be removed safely: {reason:#}\") });\n    continue;\n}","preventionTips":["Never hand-edit .mise-cask.toml — regenerate by reinstalling","Keep one mise version installing into a prefix; mixed-version receipts drift inconsistent","Treat inventory errors as corruption: reinstall the cask rather than patching the file"],"tags":["brew","cask","prune","receipt","manifest","consistency"],"backgroundTag":"manifest-validation-failed","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}