{"record":{"id":"f29e20b7baea0ee3","repo":"jdx/mise","slug":"brew-cask-refusing-to-restore-flight-target-throu","errorCode":null,"errorMessage":"brew-cask: refusing to restore flight target through a changed parent: {}","messagePattern":"brew-cask: refusing to restore flight target through a changed parent: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":3321,"sourceCode":"\nfn resolved_parent(path: &Path) -> Result<PathBuf> {\n    let parent = path\n        .parent()\n        .ok_or_else(|| eyre!(\"brew-cask: flight target has no parent\"))?;\n    Ok(path_with_resolved_existing_ancestor(parent))\n}\n\nfn validate_backup_parents(entry: &ArtifactLinkBackup) -> Result<()> {\n    if resolved_parent(&entry.target)? != entry.target_parent\n        || entry\n            .backup\n            .as_deref()\n            .zip(entry.backup_parent.as_ref())\n            .is_some_and(|(backup, expected)| {\n                !resolved_parent(backup).is_ok_and(|current| current == *expected)\n            })\n    {\n        bail!(\n            \"brew-cask: refusing to restore flight target through a changed parent: {}\",\n            entry.target.display()\n        );\n    }\n    Ok(())\n}\n\nfn flight_backup_parent(target: &Path) -> Result<&Path> {\n    if let Some(app) = target.ancestors().find(|ancestor| {\n        ancestor\n            .extension()\n            .is_some_and(|extension| extension.eq_ignore_ascii_case(\"app\"))\n    }) {\n        return app\n            .parent()\n            .ok_or_else(|| eyre!(\"brew-cask: app flight target has no parent\"));\n    }\n    target","sourceCodeStart":3303,"sourceCodeEnd":3339,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L3303-L3339","documentation":"Before restoring a flight backup, validate_backup_parents recomputes the resolved parent directory of both the target and (if present) the backup and compares them with the parents recorded when the backup was taken. A mismatch (parent replaced, moved, or re-symlinked in the meantime) aborts the restore so the original is not written into the wrong location.","triggerScenarios":"The parent directory of an app/artifact was deleted and recreated, renamed, or had a symlink inserted between the interrupted run and the recovery attempt.","commonSituations":"User reorganized ~/Applications or the Homebrew prefix between a crashed install and the retry; another installer rebuilt the directory; migration tools moving app containers around.","solutions":["Restore the original parent directory (recreate it, or move it back) so its resolved path matches the recorded parent, then retry","Otherwise restore manually: copy the backup file to the intended location and delete the recovery record","Avoid restructuring directories that contain in-flight cask artifacts"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use std::fs;\n\nfn backup_parents_unchanged(target: &std::path::Path, backup: Option<&std::path::Path>,\n                            expected_target_parent: &std::path::Path) -> bool {\n    fs::canonicalize(target.parent().unwrap_or_default())\n        .is_ok_and(|p| p == expected_target_parent)\n        && backup\n            .map(|b| fs::canonicalize(b.parent().unwrap_or_default()).is_ok())\n            .unwrap_or(true)\n}","typeGuard":null,"tryCatchPattern":"match recover_flight_backups() {\n    Err(e) if e.to_string().contains(\"changed parent\") => {\n        // recreate/move the original parent directory back, then retry recovery\n        return Err(e.wrap_err(\"restore the original parent dir, then re-run recovery\"));\n    }\n    other => other?,\n}","preventionTips":["Do not rename, delete, or re-symlink artifact parent directories between runs","Re-run recovery promptly after a crash, before reorganizing directories","Restore the recorded parent layout before manual recovery attempts"],"tags":["brew-cask","recovery","restore","toctou"],"backgroundTag":"toctou-race-condition","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}