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

A cask flight plan's Move step can name a source (or glob) that expands to multiple staged items. Multiple sources can only be moved when the target is an existing directory (each source lands inside it); a file target cannot receive several sources. mise validates this up front and aborts the flight step rather than guessing an implicit merge.

Source

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

}

pub(super) 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 afd2eddd3a)

Solutions

  1. Fix the cask's Move step so the target is the destination directory when the source is a glob (e.g. target the app dir, not a file path).
  2. Narrow the source glob so it matches exactly one item if a single-file target is intended.
  3. Create the target directory first (a preceding Mkdir-like step or in packaging) so target.is_dir() holds.
  4. Update the cask for the new upstream layout if packaging changed from one artifact to many.

Example fix

// before (glob source, file target)
{"move": {"source_glob": true, "source": "staged/*", "target": "/Applications/MyApp"}}
// after
{"move": {"source_glob": true, "source": "staged/*", "target": "/Applications"}}
Defensive patterns

Strategy: validation

Validate before calling

// cask authoring check: glob sources need a directory target
if source.includes('*') && !fs.existsSync(target) { // or statSync(target).isDirectory()
  throw new Error(`move target '${target}' must be a directory for glob source`);
}

Type guard

function isDirectoryTarget(target) {
  try { return require('fs').statSync(target).isDirectory(); } catch { return false; }
}

Prevention

When it happens

Trigger: execute_flight_step processes FlightStep::Move where flight_sources() yields more than one path (e.g. a glob matched several files) while resolve_flight_path(target) does not point at an existing directory. Reached via execute_flight_steps_with_completion and flight rollback tests.

Common situations: A cask stanza uses a glob ('staged/*.app' or 'bin/*') whose target stanza forgot the directory form (e.g. target '/Applications' vs '/Applications/MyApp'); upstream packaging changed so a previously single file now matches multiple files; cask author typo in artifacts definition.

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/00d670078d1193f8. Report an issue: GitHub.