jdx/mise · error
brew-cask: APPDIR artifact '{source}' must stay below Applic
Error message
brew-cask: APPDIR artifact '{source}' must stay below Applications What it means
A `$APPDIR/...` artifact source must be a plain relative path directly beneath Applications: after stripping the `$APPDIR/` prefix, the remainder must be non-empty and consist solely of normal components (no `..`, no absolute pieces). The first component names an app bundle to match against; anything else is rejected before matching begins.
Source
Thrown at src/system/packages/brew/cask.rs:4253
return Ok(source);
}
Err(eyre!(
"brew-cask: completion executable '{}' was not found",
executable
))
}
fn appdir_artifact_source(source: &str, apps: &[AppArtifact]) -> Result<Option<PathBuf>> {
let Some(relative) = source.strip_prefix("$APPDIR/") else {
return Ok(None);
};
let relative = Path::new(relative);
if relative.components().next().is_none()
|| relative
.components()
.any(|component| !matches!(component, Component::Normal(_)))
{
bail!("brew-cask: APPDIR artifact '{source}' must stay below Applications");
}
let Some(Component::Normal(bundle)) = relative.components().next() else {
return Ok(None);
};
let suffix = relative.components().skip(1).collect::<PathBuf>();
let mut matches = Vec::new();
for app in apps {
let target = app_target_path(app.target_name())?;
let bundle = Path::new(bundle);
if !path_ends_with_ignore_ascii_case(Path::new(&app.source), bundle)
&& !path_ends_with_ignore_ascii_case(&target, bundle)
{
continue;
}
let path = target.join(&suffix);
if path.is_file() {
matches.push(path);
}View on GitHub (pinned to 6f52dcdf99)
Solutions
- Rewrite the source as `$APPDIR/Bundle.app/...` using only normal path components
- Validate template expansion before it reaches the artifact definition
- For locations outside Applications, use the artifact forms and bases that support them instead of `$APPDIR`
Example fix
# before completion(source: "$APPDIR/../Shared/_myapp", ...) # after completion(source: "$APPDIR/MyApp.app/Contents/Resources/_myapp", ...)
Defensive patterns
Strategy: type-guard
Type guard
// $APPDIR sources must be plain relative paths under Applications
fn is_valid_appdir_source(source: &str) -> bool {
let Some(rel) = source.strip_prefix("$APPDIR/") else {
return true; // not an appdir source; other rules apply
};
let p = std::path::Path::new(rel);
p.components().next().is_some()
&& p.components().all(|c| matches!(c, std::path::Component::Normal(_)))
} Try / catch
if err.to_string().contains("must stay below Applications") {
// rewrite as $APPDIR/Bundle.app/... with normal components only
} Prevention
- Always start $APPDIR sources with a concrete bundle name
- Lint generated stanzas for `..`, empty segments, and doubled slashes after $APPDIR/
- Use the appropriate base for paths outside Applications
When it happens
Trigger: Sources like `$APPDIR/../X`, `$APPDIR/` (empty remainder) or `$APPDIR//x`; templating that concatenates an empty or parent-referring segment into the source string.
Common situations: Hand-edited cask stanzas; template bugs producing `$APPDIR/{}`; ported ruby stanzas that used absolute /Applications paths with parent references.
Related errors
- brew-cask: invalid structured flight path '{}'
- brew-cask: APPDIR artifact '{}' is ambiguous: {}
- brew-cask: refusing generic artifact copy outside Homebrew p
- brew-cask: invalid generic artifact parent
- brew-cask: generic artifact target '{}' must stay below {}
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/55b1207668e4cb2b.
Report an issue: GitHub.