jdx/mise · error

receipt target inventory is incomplete or duplicated

Error message

receipt target inventory is incomplete or duplicated

What it means

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.

Solutions

  1. Delete the stale version directory (and receipt) and reinstall the cask via mise so a fresh, consistent receipt is written
  2. 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
  3. Upgrade mise — older receipts may use a schema the current validator rejects; ensure schema_version 3 receipts are produced
  4. Skip pruning that cask (remove its token from the prune list); Homebrew-managed or metadata-only casks should be pruned via brew itself

Example fix

// before: hand-edited receipt with duplicate target
{"targets": [{"path": "/Applications/Foo.app"}, {"path": "/Applications/Foo.app"}], ...}
// after: reinstall to regenerate a clean receipt
mise uninstall brew-cask:foo && mise install brew-cask:foo
Defensive patterns

Strategy: validation

Validate before calling

// before planning a prune, sanity-check the receipt inventory
fn receipt_inventory_ok(receipt: &CaskReceipt) -> bool {
    let records: BTreeSet<_> = receipt.targets.iter().map(|t| &t.path).collect();
    let expected: BTreeSet<_> = receipt.standard_targets().collect();
    !expected.is_empty()
        && records.len() == receipt.targets.len()
        && records == expected
        && receipt.metadata_only_apps.iter().all(|p| receipt.apps.contains(p))
}
if !receipt_inventory_ok(&candidate.receipt) { /* skip or reinstall cask */ }

Type guard

fn has_complete_inventory(receipt: &CaskReceipt) -> bool {
    !receipt.standard_targets().next().is_none()
        && receipt.targets.len() == receipt.targets.iter().map(|t| &t.path).collect::<BTreeSet<_>>().len()
        && receipt.metadata_only_apps.iter().all(|p| receipt.apps.contains(p))
}

Try / catch

match apply_cask_prune_plan_in(&plan) {
    Ok(removed) => info!("pruned {removed} casks"),
    Err(e) if e.to_string().contains("inventory is incomplete or duplicated") => {
        warn!("skipping cask with malformed receipt: {e:#}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/27af2ad621257e31. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/state.rs:1058

    }
    if !receipt.metadata_only_apps.is_empty() {
        bail!("metadata-only app ownership cannot be proven safely during pruning");
    }
    let records = receipt
        .targets
        .iter()
        .map(|record| (record.path.clone(), record))
        .collect::<BTreeMap<_, _>>();
    let expected = receipt.standard_targets().cloned().collect::<BTreeSet<_>>();
    if expected.is_empty()
        || records.len() != receipt.targets.len()
        || records.len() != expected.len()
        || receipt
            .metadata_only_apps
            .iter()
            .any(|path| !receipt.apps.contains(path))
    {
        bail!("receipt target inventory is incomplete or duplicated");
    }
    if records.keys().any(|path| !expected.contains(path)) {
        bail!("receipt target inventory contains an unclassified path");
    }

    for path in &receipt.apps {
        let record = records
            .get(path)
            .ok_or_else(|| eyre!("missing app target record"))?;
        if record.fingerprint.kind != CaskTargetKind::Directory
            || !allowed_appdir_roots()?
                .iter()
                .any(|root| path_is_below(path, root))
            || !path.file_name().is_some_and(|name| {
                staged_app_matches_target(record, &candidate.version_dir.join(name))
            })
        {
            bail!(

View on GitHub (pinned to 533346cc37)