{"record":{"id":"60d76e5bb70f72ba","repo":"jdx/mise","slug":"brew-cask-generic-artifact-backup-changed-directo-60d76e","errorCode":null,"errorMessage":"brew-cask: generic artifact backup changed directories","messagePattern":"brew-cask: generic artifact backup changed directories","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":2549,"sourceCode":"fn 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\"))?;\n        let to_name = to\n            .file_name()\n            .ok_or_else(|| eyre!(\"brew-cask: generic artifact target has no filename\"))?;\n        nix::fcntl::renameat(&parent.fd, from_name, &parent.fd, to_name)?;\n        Ok(())\n    }\n    #[cfg(not(unix))]\n    {\n        let _ = expected_parent;\n        file::rename(from, to)\n    }\n}","sourceCodeStart":2531,"sourceCodeEnd":2567,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/mod.rs#L2531-L2567","documentation":"When mise backs up or restores a generic cask artifact via rename, it requires that both source and target live in the same directory (rename cannot cross filesystems/directories). If from.parent() differs from to.parent(), the backend refuses rather than silently falling back to an unsafe copy. This keeps backup/restore of trusted targets inside the validated parent directory.","triggerScenarios":"rename_trusted_generic_target is called with 'from' and 'to' paths whose parent directories differ; a backup path is computed in a different directory than the artifact; the source has no parent (root-level path).","commonSituations":"Internal misconfiguration where the backup dir differs from the artifact dir; artifacts recorded in a stale location from an older install so the computed backup path lands elsewhere; prefix migrations changing directory layouts between record time and operation time.","solutions":["Reinstall the cask so artifact and backup paths are recomputed consistently under the same parent directory.","Ensure the computed backup/restore path is in the same directory as the artifact (same parent) before triggering upgrade/rollback.","Clear stale artifact records from a previous prefix layout and reinstall.","Report the internal inconsistency if it persists with default configuration — it indicates a backend path-computation bug."],"exampleFix":"// before\nlet backup = prefix.join(\"backups\").join(name);   // different parent\n// after\nlet backup = artifact.parent().unwrap().join(format!(\"{}.bak\", name));  // same parent","handlingStrategy":"try-catch","validationCode":"if backup.parent() != artifact.parent() {\n    eprintln!(\"backup path must share the artifact's parent directory\");\n    std::process::exit(1);\n}","typeGuard":"fn same_parent(a: &std::path::Path, b: &std::path::Path) -> bool {\n    a.parent() == b.parent()\n}","tryCatchPattern":null,"preventionTips":["Compute backup paths from the artifact's own parent, never a separate backups dir.","Reinstall casks after prefix relocations so paths stay consistent.","Do not hand-edit artifact records.","Report persistent mismatches as a backend bug with default config."],"tags":["brew-cask","rename","path-safety","backup"],"backgroundTag":"internal-invariant-violation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}