jdx/mise · error
brew-cask: ' ' must not resolve to the filesystem root
Error message
brew-cask: {APP_DIR_ENV} '{}' must not resolve to the filesystem root What it means
The MISE_BREW_CASK_OPT_APPDIR override must not resolve to the filesystem root ('/'). The value is canonicalized (via resolve_appdir, which resolves symlinks in the existing prefix) and every spelling of root — '/', '//', '/.', or a symlink pointing at '/' — is collapsed and rejected, because a root appdir would let cask apps be linked anywhere on the filesystem (src/system/packages/brew/cask/paths.rs:194).
Solutions
- Choose a real dedicated directory, e.g. export MISE_BREW_CASK_OPT_APPDIR=/opt/homebrew-apps, and create it with mkdir -p first.
- If you wanted the default behavior, unset the variable (or set it empty) so target_app_dir falls back to /Applications.
- Check whether a symlink component resolves to '/' (ls -l or realpath on the dir); replace the value with the actual target directory.
- Never point the appdir at the filesystem root — the check exists to keep privileged cask mutations bounded.
Example fix
// before export MISE_BREW_CASK_OPT_APPDIR=/ // after mkdir -p /opt/apps && export MISE_BREW_CASK_OPT_APPDIR=/opt/apps
Defensive patterns
Strategy: validation
Validate before calling
function isValidAppdir(v) {
const p = v ? path.resolve(v) : null;
return !!p && path.isAbsolute(v) && !v.split('/').includes('..') && p !== '/';
} Type guard
function isSafeAppdir(v) {
return typeof v === "string" && v.length > 0 && path.isAbsolute(v) &&
!v.includes('..') && path.resolve(v) !== path.parse(v).root;
} Prevention
- Never set MISE_BREW_CASK_OPT_APPDIR to '/', '//', '/.', or a path whose symlinks resolve to '/'.
- Run realpath on the intended directory and assert it is not the root before exporting it.
- Create a dedicated subdirectory (mkdir -p /opt/apps) rather than trying to scope to root.
- Check symlink components with ls -l / realpath; a link to '/' makes the value invalid.
When it happens
Trigger: Setting MISE_BREW_CASK_OPT_APPDIR to '/', '//', '/.', '/Applications/..', or any path containing a symlink that resolves to '/', then running a cask install or validation that calls target_app_dir (via target_path, cask_appdir, app_target_path, or allowed_appdir_roots).
Common situations: Users attempting to disable appdir scoping by pointing it at root; shell logic that computes the dir by stripping components until nothing remains; misconfigured containers/chroots where the intended app dir is literally '/'.
Understand the failure class
Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.
Related errors
- brew-cask: ' ' must be an absolute path
- brew-cask: ' ' must not contain '..
- brew-cask: app target contains NUL
- brew-cask: app target
- brew-cask: app target
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/7388ca343a900bc3.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/packages/brew/cask/paths.rs:194
{
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).
pub(super) 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 533346cc37)