{"record":{"id":"1e65b4aedd59bf63","repo":"jdx/mise","slug":"brew-cask-refusing-operation-through-a-changed-ge","errorCode":null,"errorMessage":"brew-cask: refusing operation through a changed generic artifact parent: {}","messagePattern":"brew-cask: refusing operation through a changed generic artifact parent: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":2602,"sourceCode":"            .file_name()\n            .ok_or_else(|| eyre!(\"brew-cask: generic artifact target has no filename\"))?;\n        remove_all_at(&parent.fd, name)\n    }\n    #[cfg(not(unix))]\n    {\n        let _ = expected_parent;\n        file::remove_all(target)\n    }\n}\n\n#[cfg(unix)]\nfn validate_trusted_operation_parent(\n    parent: &TrustedOperationParent,\n    expected_parent: &Path,\n) -> Result<()> {\n    let actual_parent = std::fs::canonicalize(parent.path()?)?;\n    if actual_parent != expected_parent {\n        bail!(\n            \"brew-cask: refusing operation through a changed generic artifact parent: {}\",\n            expected_parent.display()\n        );\n    }\n    Ok(())\n}\n\nfn rename_trusted_generic_target(from: &Path, to: &Path, expected_parent: &Path) -> Result<()> {\n    #[cfg(unix)]\n    {\n        if from.parent() != to.parent() {\n            bail!(\"brew-cask: generic artifact backup changed directories\");\n        }\n        let parent = open_trusted_operation_parent(from, true, false)?;\n        validate_trusted_operation_parent(&parent, expected_parent)?;\n        let from_name = from\n            .file_name()\n            .ok_or_else(|| eyre!(\"brew-cask: generic artifact source has no filename\"))?;","sourceCodeStart":2584,"sourceCodeEnd":2620,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L2584-L2620","documentation":"validate_trusted_operation_parent re-canonicalizes the path of the directory fd opened by open_trusted_operation_parent and compares it to the parent recorded when the transaction started. A mismatch means the directory now reachable at that path is not the same one bound earlier (renamed, recreated, or reached through a changed symlink such as the prefix itself), so the pending rename/remove against the held fd is refused — a TOCTOU guard on generic artifact backup/rename operations.","triggerScenarios":"The artifact parent directory or a symlinked ancestor (the prefix) is renamed or retargeted while an install transaction is in flight; concurrent mise/brew operations restructuring the prefix subtree between the trusted walk and the mutation.","commonSituations":"Two package managers mutating /opt/homebrew concurrently; a prefix migration happening mid-operation; parallel CI jobs racing cask installs against the same prefix.","solutions":["Re-run the cask operation once the concurrent change has settled","Stop overlapping mise/brew runs against the same prefix — serialize them with a lock","If you deliberately changed a prefix symlink, finish or uninstall pending casks first, then retry","If it recurs without concurrency, audit what renames the printed parent path (cleanup daemons, overlay fs) and report it"],"exampleFix":"# before: concurrent mutation\n$ brew reinstall foo & mise brew install --cask bar &\n\n# after: serialized\n$ brew reinstall foo && mise brew install --cask bar","handlingStrategy":"retry","validationCode":"// Snapshot the parent identity before the operation and compare after.\nlet before = std::fs::canonicalize(parent_dir)?;\n// ... run install ...\nlet after = std::fs::canonicalize(parent_dir)?;\nassert_eq!(before, after, \"parent changed during operation\");","typeGuard":null,"tryCatchPattern":"match run_install() {\n    Err(e) if e.to_string().contains(\"changed generic artifact parent\") => {\n        // directory was retargeted mid-flight: settle the environment, retry\n        retry_after_concurrency_settles()\n    }\n    other => other,\n}","preventionTips":["Don't rename or retarget prefix/appdir symlinks while installs are running","Serialize package-manager mutations of the prefix","Re-run the operation after any intentional prefix change"],"tags":["brew-cask","toctou","race-condition","rename","security"],"backgroundTag":"parent-directory-changed","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}