jdx/mise · error · eyre::Report
brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesy
Error message
brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root What it means
target_app_dir canonicalizes the MISE_BREW_CASK_OPT_APPDIR override (resolve_appdir follows symlinks in the longest existing prefix) and then requires at least one Normal path component. Every spelling of the filesystem root ('/', '//', '/.', a symlink to '/') collapses to '/' with no Normal components and is rejected: an appdir of root would make Path::starts_with(appdir) true for every path, i.e. disable the containment boundary entirely for privileged writes.
Source
Thrown at src/system/packages/brew/cask.rs:6059
{
bail!(
"brew-cask: {APP_DIR_ENV} '{}' must not contain '..'",
dir.display()
);
}
// Resolve the override to a real absolute path: canonicalize its longest
// existing prefix and re-append the components that do not exist yet. This
// makes the appdir a symlink-free containment boundary — privileged cask
// mutations then operate on resolved paths and cannot be redirected through
// a symlinked component — and it collapses every spelling of the filesystem
// root (`/`, `//`, `/.`, a symlink to `/`, ...) to `/` so they can all be
// rejected together.
let resolved = resolve_appdir(&dir);
if !resolved
.components()
.any(|component| matches!(component, Component::Normal(_)))
{
bail!(
"brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root",
dir.display()
);
}
Ok(resolved)
}
/// Resolve `dir` by canonicalizing its longest existing ancestor and
/// re-appending the not-yet-existing tail. Symlinks in the existing portion are
/// followed, so the result is a real path the caller can safely use as a
/// containment boundary. Falls back to `dir` unchanged if nothing along the
/// path can be canonicalized (not expected for an absolute path, where `/`
/// always resolves).
fn resolve_appdir(dir: &Path) -> PathBuf {
for ancestor in dir.ancestors() {
if let Ok(real) = ancestor.canonicalize() {
let tail = dir.strip_prefix(ancestor).unwrap_or(Path::new(""));
return real.join(tail);View on GitHub (pinned to 9dcfcaa0dc)
Solutions
- Point the override at a real subdirectory: `export MISE_BREW_CASK_OPT_APPDIR=/opt/MyApps`
- Verify what the value resolves to: `realpath "$MISE_BREW_CASK_OPT_APPDIR"` must not be '/'
- Or unset the variable to use the default /Applications
Example fix
# before export MISE_BREW_CASK_OPT_APPDIR=/ # after export MISE_BREW_CASK_OPT_APPDIR=/Applications
Defensive patterns
Strategy: validation
Validate before calling
fn appdir_env_not_root(val: &str) -> bool {
let resolved = std::path::Path::new(val)
.canonicalize()
.unwrap_or_else(|_| std::path::PathBuf::from(val));
resolved.components().any(|c| matches!(c, std::path::Component::Normal(_)))
} Try / catch
match target_app_dir() {
Ok(dir) => dir,
Err(e) if e.to_string().contains("filesystem root") => {
std::env::remove_var("MISE_BREW_CASK_OPT_APPDIR");
target_app_dir()
}
Err(e) => Err(e),
} Prevention
- Point MISE_BREW_CASK_OPT_APPDIR at a real subdirectory, never '/', '//', or a root symlink
- Verify with realpath "$MISE_BREW_CASK_OPT_APPDIR" before launching mise
When it happens
Trigger: Exporting MISE_BREW_CASK_OPT_APPDIR="/", "//", "/." or a symlink that resolves to the filesystem root.
Common situations: Scripts programmatically building the appdir from an empty or '/' base path; users experimenting with the override; symlinked dirs whose canonical target is root.
Related errors
- brew-cask: app target '{target_name}' must be under {}
- brew-cask: {APP_DIR_ENV} '{}' must be an absolute path
- brew-cask: {APP_DIR_ENV} '{}' must not contain '..'
- brew-cask: invalid appdir '{}'
- brew-cask: app target contains NUL
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/5f49de3979421b8c.
Report an issue: GitHub.