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

  1. Rewrite the source as `$APPDIR/Bundle.app/...` using only normal path components
  2. Validate template expansion before it reaches the artifact definition
  3. 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

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


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/55b1207668e4cb2b. Report an issue: GitHub.