{"record":{"id":"619e49b7f7290b7b","repo":"jdx/mise","slug":"brew-cask-invalid-completion-target","errorCode":null,"errorMessage":"brew-cask: invalid completion target '{}'","messagePattern":"brew-cask: invalid completion target '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":4510,"sourceCode":"        }\n    };\n    if normalized.is_empty() {\n        bail!(\"brew-cask: invalid completion target '{target_name}'\");\n    }\n    Ok(normalized)\n}\n\nfn caskroom_completion_path(caskroom: &Path, target: &Path) -> Result<PathBuf> {\n    let prefix = prefix::prefix();\n    let relative = target.strip_prefix(&prefix).map_err(|_| {\n        eyre!(\n            \"brew-cask: completion target '{}' must be under {}\",\n            target.display(),\n            prefix.display()\n        )\n    })?;\n    if relative.components().next().is_none() {\n        bail!(\n            \"brew-cask: invalid completion target '{}'\",\n            target.display()\n        );\n    }\n    Ok(caskroom.join(relative))\n}\n\nfn completion_target_paths(cask: &Cask, artifacts: &CaskArtifacts) -> Result<Vec<PathBuf>> {\n    let mut targets = artifacts\n        .completions\n        .iter()\n        .map(CompletionArtifact::target_path)\n        .collect::<Result<Vec<_>>>()?;\n    for generated in &artifacts.generated_completions {\n        targets.extend(generated.target_paths(cask)?);\n    }\n    targets.sort();\n    targets.dedup();","sourceCodeStart":4492,"sourceCodeEnd":4528,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L4492-L4528","documentation":"caskroom_completion_path maps a validated completion target into the cask's private caskroom by stripping the brew prefix from it. If stripping leaves an empty relative path — i.e. the resolved target is exactly the brew prefix itself — there is no file component to relocate and mise bails. It is the degenerate-target guard between the strip_prefix containment check and the final caskroom.join.","triggerScenarios":"A completion target that resolves to the brew prefix root: an absolute target equal to the prefix (e.g. '/opt/homebrew'), or one that normalizes to it. `relative.components().next().is_none()` at src/system/packages/brew/cask.rs:4510 is true only when strip_prefix consumed the entire path.","commonSituations":"Cask metadata where the target field accidentally holds the prefix or '/' after templating; hand-written stanzas that pasted a directory instead of a file path; programmatic cask generation that emits the install root when a variable is empty.","solutions":["Point the completion target at a concrete file under the prefix, e.g. 'share/zsh/site-functions/_tool', not a directory or the prefix itself.","Check how the target value was produced — a leading variable that expanded to empty often yields the bare prefix.","Add an authoring-time lint: target must contain at least one path component below the prefix."],"exampleFix":"# before — target resolves to the prefix itself\n\"target\": \"/opt/homebrew\"\n\n# after\n\"target\": \"/opt/homebrew/share/zsh/site-functions/_tool\"","handlingStrategy":"validation","validationCode":"# target must have at least one component below the brew prefix\nPREFIX=\"$(brew --prefix)\"\ncase \"$TARGET\" in \"$PREFIX\"|\"$PREFIX/\") echo 'target is the prefix itself'; exit 1;; esac","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always author completion targets as concrete file paths, never directories or roots.","When templating target paths, assert the rendered value differs from the prefix.","Prefer relative targets so mise derives the location itself."],"tags":["brew-cask","completions","path-validation","mise"],"backgroundTag":"invalid-target-path","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}