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
- 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).
- Narrow the source glob so it matches exactly one item if a single-file target is intended.
- Create the target directory first (a preceding Mkdir-like step or in packaging) so target.is_dir() holds.
- 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
- Pair glob/multi-file Move sources with a directory target in cask artifacts.
- Test cask flight plans against the real staged layout before publishing.
- Re-validate casks when upstream packaging changes file counts.
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
- brew-cask: invalid {kind} '{value}'
- remote rustc action metadata is invalid
- remote action output directory is invalid
- invalid task action identity
- invalid action prediction adapter
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/00d670078d1193f8.
Report an issue: GitHub.