jdx/mise · error

brew-cask: structured copy source must resolve to exactly on

Error message

brew-cask: structured copy source must resolve to exactly one path

What it means

During a brew-cask structured copy flight step, the source selector is expected to resolve to exactly one path. When `source_glob` is enabled, the glob is expanded and this error fires if it matches zero or more than one path, because a copy step copies a single artifact onto a single target path. This is a cask-definition sanity check to avoid ambiguous copies.

Source

Thrown at src/system/packages/brew/cask/flight.rs:651

                        file::remove_file_or_dir(&path)?;
                    }
                }
            }
        }
        FlightStep::Copy {
            source,
            target,
            recursive,
            overwrite,
            source_glob,
            guards,
        } => {
            if !flight_guards_pass(cask, guards, staged_path, appdir)? {
                return Ok(());
            }
            let sources = flight_symlink_sources(cask, source, *source_glob, staged_path, appdir)?;
            let [source] = sources.as_slice() else {
                bail!("brew-cask: structured copy source must resolve to exactly one path");
            };
            if !source.exists() {
                bail!(
                    "brew-cask: structured copy source '{}' was not found",
                    source.display()
                );
            }
            if source.is_dir() && !recursive {
                bail!("brew-cask: structured directory copy requires recursive=true");
            }
            let target = resolve_flight_path_with_context(cask, target, staged_path, appdir)?;
            let external = !target.starts_with(staged_path);
            let target_metadata = target.symlink_metadata().ok();
            if target_metadata.is_some() {
                if !overwrite {
                    bail!(
                        "brew-cask: structured copy target '{}' already exists",
                        target.display()

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Tighten the copy source glob so it matches exactly one path (add distinguishing segments, e.g. " MyApp.app" instead of "*.app").
  2. If multiple matches are intended, replace the single Copy step with a Symlink step (which supports directory targets and multiple sources) or several Copy steps.
  3. If the glob matched nothing, verify the file exists in the staged path at this install step (it may only appear after a prior Move/Copy step).

Example fix

// before
FlightStep::Copy { source: glob("**/*.app"), source_glob: true, .. }
// after
FlightStep::Copy { source: path("MyApp.app"), source_glob: false, .. }
Defensive patterns

Strategy: validation

Validate before calling

let sources = flight_symlink_sources(cask, source, true, staged_path, appdir)?;
if sources.len() != 1 {
    return Err(eyre!("copy source glob must match exactly one path, got {}", sources.len()));
}

Prevention

When it happens

Trigger: A FlightStep::Copy with source_glob=true whose pattern matches 0 or >=2 files inside staged_path (e.g. "*.app" matching two bundles, or a typo matching nothing). A non-glob Copy never hits this branch.

Common situations: Cask authors writing glob-based copy steps after an upstream app rename added a second matching bundle; version changes altering staged contents so a formerly unique glob now matches multiple files or none; copy-pasting a Symlink-style multi-match glob into a Copy step.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/cc6932cccb1d1de3. Report an issue: GitHub.