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
- 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
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
- 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
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
- artifact target has changed
- receipt target inventory contains an unclassified path
- app target is outside an allowed Applications directory
- binary target is not an owned Caskroom symlink
- brew-cask: ' ' must stay below Applications
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)