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
- 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)
- Tighten the glob so it matches exactly one file, or split into one single-source move step per file
- If the target is intentionally a file, remove `source_glob: true` and reference the exact file name
- 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 authoring globbed move steps, always target a directory a previous step guarantees exists
- Dry-run glob expansion against the staged payload before install
- Keep one artifact per file whenever the target must be a file path
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
- brew-cask: structured copy source must resolve to exactly on
- brew-cask: structured symlink source '{}' did not match any
- brew-cask: structured move source '{}' was not found
- brew-cask: structured copy source '{}' was not found
- brew-cask: structured directory copy requires recursive=true
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/e06a65c6e6c02654.
Report an issue: GitHub.