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

mise's native Homebrew cask installer executes structured 'flight' (artifact) steps when installing a cask. A copy step resolves its source via flight_symlink_sources, which supports globs, but copy semantics allow exactly one source path. If the resolved list is empty or contains more than one entry, the install aborts before touching the target.

Source

Thrown at src/system/packages/brew/cask.rs:3437

            source,
            target,
            recursive,
            overwrite,
            source_glob,
            guards,
        } => {
            if !guards
                .iter()
                .map(|guard| flight_guard_matches(cask, guard, staged_path, appdir))
                .collect::<Result<Vec<_>>>()?
                .into_iter()
                .all(|matches| matches)
            {
                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 9dcfcaa0dc)

Solutions

  1. List the staged directory (e.g. "$(brew --caskroom)/<token>/<version>") and see what the source pattern actually matches
  2. Tighten the copy source in the cask artifact data so it selects exactly one path, or drop source_glob and name the file literally
  3. If several files must be handled, split into one copy step per file, or use a symlink step which supports multiple sources
  4. If the upstream cask metadata itself is wrong, report it to homebrew-cask and pin the previous cask version until fixed

Example fix

// before (cask artifact data)
copy { source: "payload/*.app", source_glob: true, target: { base: "appdir", path: "MyApp.app" } }
// after
copy { source: "payload/MyApp.app", source_glob: false, target: { base: "appdir", path: "MyApp.app" } }
Defensive patterns

Strategy: validation

Validate before calling

STAGED="$(brew --caskroom)/<token>/<version>"
n=$(find "$STAGED/payload" -maxdepth 1 -name '*.app' 2>/dev/null | wc -l)
if [ "$n" -ne 1 ]; then echo "copy glob matches $n paths"; fi

Prevention

When it happens

Trigger: A copy artifact whose source_glob=true pattern expands to zero or two-or-more paths under the staged caskroom directory, e.g. source 'payload/*.app' matching two .app bundles, or a brace pattern '{a,b}/**' where both alternatives exist.

Common situations: Homebrew cask API metadata whose glob was written against Homebrew's staging layout but mismatches mise's staged tree; a cask version bump renames payloads so a previously-single glob now matches two files; brace expansions where both branches are present in the archive.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/70653fe9062f4476. Report an issue: GitHub.