{"record":{"id":"4d69792d3c3fc2fa","repo":"jdx/mise","slug":"brew-cask-structured-symlink-globs-must-use-stage","errorCode":null,"errorMessage":"brew-cask: structured symlink globs must use staged_path","messagePattern":"brew-cask: structured symlink globs must use staged_path","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":3854,"sourceCode":"        FlightGuard::IfExists(path) => {\n            Ok(resolve_flight_path_with_context(cask, path, staged_path, appdir)?.exists())\n        }\n        FlightGuard::UnlessExists(path) => {\n            Ok(!resolve_flight_path_with_context(cask, path, staged_path, appdir)?.exists())\n        }\n    }\n}\n\nfn flight_symlink_sources(\n    cask: &Cask,\n    source: &FlightPath,\n    source_glob: bool,\n    staged_path: &Path,\n    appdir: &Path,\n) -> Result<Vec<PathBuf>> {\n    if source_glob {\n        if source.base != FlightPathBase::StagedPath {\n            bail!(\"brew-cask: structured symlink globs must use staged_path\");\n        }\n        let pattern = expand_flight_template(cask, &source.path, staged_path, appdir);\n        return expand_staged_glob(staged_path, &pattern);\n    }\n    Ok(vec![resolve_flight_path_with_context(\n        cask,\n        source,\n        staged_path,\n        appdir,\n    )?])\n}\n\nfn create_flight_symlink(source: &Path, target: &Path, sudo: FlightSudo) -> Result<()> {\n    match sudo {\n        FlightSudo::Never => file::make_symlink(source, target).map(|_| ()),\n        FlightSudo::IfNeeded => make_symlink_elevating(source, target),\n        FlightSudo::Always => sudo::run(\"/bin/ln\", &symlink_command_args(source, target), &[]),\n    }","sourceCodeStart":3836,"sourceCodeEnd":3872,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L3836-L3872","documentation":"`flight_symlink_sources` only implements glob expansion against the staged cask directory. A copy or symlink step with `source_glob: true` whose source base is `AppDir`, `HomebrewPrefix` or `Literal` is rejected before any glob runs: only `FlightPathBase::StagedPath` is valid for globbed sources.","triggerScenarios":"A cask stanza combining a non-staged base (an `$APPDIR`/`#{appdir}`-anchored source) with glob metacharacters; programmatic construction of a FlightPath with a non-staged base and `source_glob: true`.","commonSituations":"Hand-written or ported Homebrew ruby stanzas that glob absolute or appdir paths; templating that prefixes the appdir onto a wildcard pattern.","solutions":["Drop the glob metacharacters and use a literal path with the non-staged base","Keep the glob but express the source relative to the staged payload (StagedPath base)","Restructure the artifact so only staged-payload files are ever globbed"],"exampleFix":"# before\nsymlink(source: \"#{appdir}/*.app/Contents/MacOS/tool\", source_glob: true, target: \"#{prefix}/bin/tool\")\n# after - glob runs against the staged payload\nsymlink(source: \"*.app/Contents/MacOS/tool\", source_glob: true, target: \"#{prefix}/bin/tool\")","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Narrow FlightPath values before combining a glob with a base\nfn glob_source_is_supported(source: &FlightPath, source_glob: bool) -> bool {\n    !source_glob || matches!(source.base, FlightPathBase::StagedPath)\n}","tryCatchPattern":"if err.to_string().contains(\"symlink globs must use staged_path\") {\n    // strip the glob and use a literal path, or rebase the source onto the staged payload\n}","preventionTips":["Never prefix glob patterns with appdir or absolute prefixes","Lint cask stanzas for glob metacharacters in non-staged sources","Keep wildcards scoped to the staged payload only"],"tags":["brew-cask","glob","path-base","symlink"],"backgroundTag":"invalid-base-path","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}