jdx/mise · error

brew-cask: completion target

Error message

brew-cask: completion target '{}' must be under {}

What it means

This error enforces the containment part of the same path-safety check: after resolving the completion target (absolute, prefix-joined, or default-dir joined), the path must remain under the Homebrew prefix. If it starts outside the prefix (e.g. /etc/..., /Users/..., or an absolute path into another tree), the install is rejected to prevent casks from writing outside the managed prefix.

Solutions

  1. Change the target to use the $HOMEBREW_PREFIX placeholder instead of a hard-coded absolute path.
  2. Verify the Homebrew prefix on this machine (brew --prefix) and make the target relative or prefix-based so it resolves under it.
  3. If the file must live outside the prefix, install it out-of-band rather than through the cask completion mechanism.
  4. Update a stale cask recipe that assumes the /usr/local prefix to the prefix-independent form.

Example fix

// before (Intel-style absolute path on Apple Silicon)
completions = [{ target = "/usr/local/etc/bash_completion.d/mytool" }]
// after
completions = [{ target = "$HOMEBREW_PREFIX/etc/bash_completion.d/mytool" }]
Defensive patterns

Strategy: validation

Validate before calling

let target = resolve_target(target_name)?; // apply $HOMEBREW_PREFIX substitution first
if !target.starts_with(&prefix) {
    return Err(format!("target {} is outside prefix {}", target.display(), prefix.display()));
}

Prevention

When it happens

Trigger: A cask completion target declared as an absolute path outside the Homebrew prefix (e.g. /etc/bash_completion.d/foo or /usr/local/share/... when the prefix is /opt/homebrew), or an absolute target that does not begin with the current prefix after $HOMEBREW_PREFIX substitution.

Common situations: A cask written for an Intel Homebrew layout (/usr/local) being installed on Apple Silicon (/opt/homebrew); hard-coded absolute paths in a hand-rolled cask; a $HOMEBREW_PREFIX substitution producing a non-prefix root; shared cask recipes copied between systems with different prefixes.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/a768c3cf2c2c3fc0. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask/mod.rs:3178

    let path = PathBuf::from(&target_name);
    let target = if path.is_absolute() {
        path
    } else if target_name.contains('/') {
        prefix.join(path)
    } else {
        default_completion_dir(shell).join(completion_filename(shell, &target_name)?)
    };
    if target
        .components()
        .any(|component| matches!(component, Component::ParentDir))
    {
        bail!(
            "brew-cask: completion target '{}' must not contain '..'",
            target.display()
        );
    }
    if !target.starts_with(&prefix) {
        bail!(
            "brew-cask: completion target '{}' must be under {}",
            target.display(),
            prefix.display()
        );
    }
    Ok(target)
}

fn generated_completion_target_path(shell: CompletionShell, base_name: &str) -> Result<PathBuf> {
    match shell {
        CompletionShell::Pwsh => {
            let name = format!("_{}.ps1", base_name);
            completion_target_path(shell, &name)
        }
        _ => completion_target_path(shell, base_name),
    }
}

View on GitHub (pinned to 533346cc37)