jdx/mise · error

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

Error message

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

What it means

A structured `copy` artifact step requires its source to resolve to exactly one path. The step resolves sources through `flight_symlink_sources` (honoring `source_glob` and appdir context), then destructures with `let [source] = sources`; zero matches or multiple matches both fail this check and abort the cask install before any file is copied.

Solutions

  1. Pin the glob to the exact file name so it matches exactly one path
  2. Split into one copy step per file when several files must be copied
  3. If the glob matched zero, inspect the staged payload and re-stage (fresh download) to rule out partial extraction
  4. Guard genuinely optional files with `if_exists` so the step skips instead of failing

Example fix

# before
copy(source: "payload/**/*", target: "#{staged}/out", source_glob: true)
# after - exactly one concrete source per step
copy(source: "payload/MyApp.app", target: "#{staged}/out/MyApp.app")
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: a copy step must resolve to exactly one source
let sources = flight_symlink_sources(cask, &step.source, step.source_glob, staged_path, appdir)?;
match sources.as_slice() {
    [single] => Ok(single.clone()),
    [] => Err(eyre!("copy source '{}' matched nothing; check payload layout", step.source.path)),
    many => Err(eyre!("copy source '{}' matched {} paths; split into per-file steps", step.source.path, many.len())),
}

Try / catch

if err.to_string().contains("copy source must resolve to exactly one path") {
    // report the staged directory listing next to the error to speed up diagnosis
}

Prevention

When it happens

Trigger: `copy` with `source_glob: true` where the staged glob matches zero or 2+ paths; a source string whose template expansion resolves to several staged locations. All `guards` must have matched first, so this fires on the normal path of a mis-scoped glob.

Common situations: Upstream payload gains or loses a file so a previously exact glob becomes ambiguous or empty; overly broad patterns like `**/*.app`; version-stamped file names changing between releases.

Related errors


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

Appendix: source

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

            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 6f52dcdf99)