jdx/mise · error
brew-cask: structured copy source must resolve to exactly on
Error message
brew-cask: structured copy source must resolve to exactly one path
What it means
mise's native Homebrew cask installer executes structured 'flight' (artifact) steps when installing a cask. A copy step resolves its source via flight_symlink_sources, which supports globs, but copy semantics allow exactly one source path. If the resolved list is empty or contains more than one entry, the install aborts before touching the target.
Source
Thrown at src/system/packages/brew/cask.rs:3437
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 9dcfcaa0dc)
Solutions
- List the staged directory (e.g. "$(brew --caskroom)/<token>/<version>") and see what the source pattern actually matches
- Tighten the copy source in the cask artifact data so it selects exactly one path, or drop source_glob and name the file literally
- If several files must be handled, split into one copy step per file, or use a symlink step which supports multiple sources
- If the upstream cask metadata itself is wrong, report it to homebrew-cask and pin the previous cask version until fixed
Example fix
// before (cask artifact data)
copy { source: "payload/*.app", source_glob: true, target: { base: "appdir", path: "MyApp.app" } }
// after
copy { source: "payload/MyApp.app", source_glob: false, target: { base: "appdir", path: "MyApp.app" } } Defensive patterns
Strategy: validation
Validate before calling
STAGED="$(brew --caskroom)/<token>/<version>" n=$(find "$STAGED/payload" -maxdepth 1 -name '*.app' 2>/dev/null | wc -l) if [ "$n" -ne 1 ]; then echo "copy glob matches $n paths"; fi
Prevention
- Dry-run copy source globs against the staged directory before install and require exactly one match
- Avoid brace expansions in copy sources unless only one alternative can ever exist
- Review cask artifact data after payload version bumps that rename files
When it happens
Trigger: A copy artifact whose source_glob=true pattern expands to zero or two-or-more paths under the staged caskroom directory, e.g. source 'payload/*.app' matching two .app bundles, or a brace pattern '{a,b}/**' where both alternatives exist.
Common situations: Homebrew cask API metadata whose glob was written against Homebrew's staging layout but mismatches mise's staged tree; a cask version bump renames payloads so a previously-single glob now matches two files; brace expansions where both branches are present in the archive.
Related errors
- brew-cask: structured directory copy requires recursive=true
- brew-cask: structured copy source '{}' was not found
- brew-cask: refusing generic artifact source outside the extr
- brew-cask: refusing to stage generic artifact through a path
- brew-cask: unsupported generic artifact type: {}
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/70653fe9062f4476.
Report an issue: GitHub.