jdx/mise · error

brew-cask: structured flight glob '{}' matched outside stage

Error message

brew-cask: structured flight glob '{}' matched outside staged path

What it means

Every path matched by a flight glob must stay under the staged cask directory. After expansion, each match is checked with `path.starts_with(staged_path)` and any escape aborts the install, enforcing the sandbox invariant that structured steps touch only staged files. In practice this fires when the staged root contains symlinked components or the pattern yields a lexically different root than the checker expects.

Source

Thrown at src/system/packages/brew/cask.rs:3943

    let escaped_root = glob::Pattern::escape(staged_path.to_string_lossy().as_ref());
    for pattern in expand_braces(pattern) {
        validate_flight_relative_path(&pattern)?;
        let rooted_pattern = Path::new(&escaped_root)
            .join(Path::new(&pattern))
            .to_string_lossy()
            .to_string();
        for path in glob::glob_with(
            &rooted_pattern,
            glob::MatchOptions {
                require_literal_separator: true,
                ..Default::default()
            },
        )
        .wrap_err_with(|| format!("brew-cask: invalid structured flight glob '{pattern}'"))?
        {
            let path = path?;
            if !path.starts_with(staged_path) {
                bail!(
                    "brew-cask: structured flight glob '{}' matched outside staged path",
                    pattern
                );
            }
            matches.push(path);
        }
    }
    matches.sort();
    matches.dedup();
    Ok(matches)
}

fn is_flight_glob(path: &str) -> bool {
    path.chars()
        .any(|c| matches!(c, '*' | '?' | '[' | ']' | '{' | '}'))
}

fn resolve_flight_path(staged_path: &Path, path: &FlightPath) -> Result<PathBuf> {

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Ensure the staged path used for globbing is the same real, non-symlinked root the matches are checked against (canonicalize the staged root)
  2. Remove `..` and absolute segments from glob patterns in the cask definition
  3. Re-stage the cask so staging metadata matches the on-disk location

Example fix

// before
let staged = "/tmp/caskroom/myapp"; // /tmp is a symlink on macOS
// after
let staged = std::fs::canonicalize("/tmp/caskroom/myapp")?;
Defensive patterns

Strategy: validation

Validate before calling

// Canonicalize the staged root before any glob expansion so matches stay comparable
let staged_real = std::fs::canonicalize(staged_path)?;
for m in expand_staged_glob(&staged_real, pattern)? {
    debug_assert!(m.starts_with(&staged_real), "glob escaped staged root: {}", m.display());
}

Try / catch

if err.to_string().contains("matched outside staged path") {
    // re-stage the cask; this indicates a symlinked/moved staging root, not a retryable error
}

Prevention

When it happens

Trigger: The staged root is reached through a symlink the glob resolves differently than the prefix check expects (e.g. /tmp versus /private/tmp on macOS); patterns containing `..` or brace expansions producing rooted variants; a corrupted or adversarial cask stanza.

Common situations: A custom staging directory configured under a symlinked path; the staging directory moved between download and install; a hand-edited stanza with traversal segments.

Related errors


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