{"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":2476,"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":2458,"sourceCodeEnd":2494,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L2458-L2494","documentation":"Rename/unlink helpers for generic artifacts open the parent directory as a descriptor at transaction start and, right before acting, canonicalize the descriptor's path and compare it with the expected parent recorded earlier. A mismatch means the directory reachable at that name was replaced or moved mid-operation, so the rename is refused rather than performed through an unexpected directory.","triggerScenarios":"The parent directory of a generic artifact is deleted and recreated, renamed, or re-symlinked between the start of the artifact operation and the rename/restore step; concurrent installers writing the same directory.","commonSituations":"Parallel package managers or manual file management touching ~/Applications or the prefix while a cask install runs; a crashed prior attempt leaving a replacement directory; automation scripts that rebuild directories mid-install.","solutions":["Retry the cask operation once no other process is modifying that parent directory","Remove and recreate the parent directory cleanly (with correct ownership), then re-run the install","Serialize automation so only one installer manipulates the target tree at a time"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"use std::fs;\n\n// Before the operation, confirm the parent still resolves to the expected directory\n// and is not world-writable (cheap pre-flight for the identity check)\nfn parent_matches_expected(parent: &std::path::Path, expected: &std::path::Path) -> bool {\n    fs::canonicalize(parent).is_ok_and(|p| p == expected)\n}","typeGuard":null,"tryCatchPattern":"match rename_trusted_generic_target(from, to, expected_parent) {\n    Err(e) if e.to_string().contains(\"changed generic artifact parent\") => {\n        // the parent dir was replaced mid-run: recreate a clean parent and retry once\n        std::thread::sleep(std::time::Duration::from_secs(1));\n        rename_trusted_generic_target(from, to, expected_parent)?\n    }\n    other => other?,\n}","preventionTips":["Serialize installers so only one process mutates a given parent directory","Do not delete/recreate artifact parent directories while installs are in flight","Re-run the cask operation after cleaning up a crashed attempt"],"tags":["brew-cask","toctou","rename","race-condition"],"backgroundTag":"toctou-race-condition","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}