jdx/mise · error

brew-cask: structured move with multiple sources requires a

Error message

brew-cask: structured move with multiple sources requires a directory target

What it means

Raised while executing a structured `move` artifact step of a Homebrew cask. When the step's source is a glob that expands to more than one file, every entry is moved into the target, which is only possible if the target already exists as a directory (Unix `mv a b dir/` semantics). If the resolved target is missing or is a regular file, the step aborts before any file is touched.

Source

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

}

fn execute_flight_step(
    cask: &Cask,
    step: &FlightStep,
    staged_path: &Path,
    appdir: &Path,
    targets: &mut FlightTargetTransaction,
) -> Result<()> {
    match step {
        FlightStep::Move {
            source,
            target,
            source_glob,
        } => {
            let sources = flight_sources(staged_path, source, *source_glob)?;
            let target = resolve_flight_path(staged_path, target)?;
            if sources.len() > 1 && !target.is_dir() {
                bail!(
                    "brew-cask: structured move with multiple sources requires a directory target"
                );
            }
            for source in sources {
                let target = if target.is_dir() {
                    target.join(source.file_name().ok_or_else(|| {
                        eyre!(
                            "brew-cask: structured move source '{}' has no file name",
                            source.display()
                        )
                    })?)
                } else {
                    target.clone()
                };
                if let Some(parent) = target.parent()
                    && !parent.as_os_str().is_empty()
                {
                    file::create_dir_all(parent)?;

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Ensure the target directory exists before the move: add an earlier create/move step, or target a directory that already exists (e.g. the appdir)
  2. Tighten the glob so it matches exactly one file, or split into one single-source move step per file
  3. If the target is intentionally a file, remove `source_glob: true` and reference the exact file name
  4. List the staged caskroom directory to confirm how many files the glob actually matches on this payload

Example fix

# cask artifact stanza - before
move(source: "*.app", target: "#{appdir}/MyApp.app", source_glob: true)
# after: target is an existing directory that receives both bundles
move(source: "*.app", target: "#{appdir}", source_glob: true)
Defensive patterns

Strategy: validation

Validate before calling

// Before running the flight step, verify multi-source moves have a directory target
let sources = expand_staged_glob(staged_path, &step.source.path)?;
if sources.len() > 1 {
    let target = resolve_flight_path(staged_path, &step.target)?;
    if !target.is_dir() {
        return Err(eyre!("target '{}' must be an existing directory for a multi-source move", target.display()));
    }
}

Try / catch

// These are untyped eyre bail!()s; classify on the message
let msg = err.to_string();
if msg.contains("multiple sources requires a directory target") {
    // create the target directory or narrow the glob, then re-run the step
} else {
    return Err(err);
}

Prevention

When it happens

Trigger: A `move` step with `source_glob: true` whose pattern matches 2+ paths (e.g. two `*.app` bundles in the staged payload) while the target path does not exist or resolves to a file. Also fires when an earlier directory-creation step was skipped because its guard (e.g. `unless_exists`) failed on this machine.

Common situations: A cask starts shipping two apps where it previously shipped one, so the old single-file target is now invalid; the staged payload layout changed between cask versions; the step that creates the target directory is OS-guarded and the install runs on the other OS.

Related errors


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