{"record":{"id":"89688e17a6cb47fa","repo":"jdx/mise","slug":"brew-cask-refusing-to-restore-flight-target-throu-89688e","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/flight.rs","lineNumber":541,"sourceCode":"}\n\npub(super) fn 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\npub(super) fn validate_backup_parents(entry: &ArtifactLinkBackup) -> Result<()> {\n    let backup_parent_matches = match (&entry.backup, &entry.backup_parent) {\n        (Some(backup), Some(expected)) => {\n            resolved_parent(backup).is_ok_and(|current| current == *expected)\n        }\n        (None, None) => true,\n        _ => false,\n    };\n    if resolved_parent(&entry.target)? != entry.target_parent || !backup_parent_matches {\n        bail!(\n            \"brew-cask: refusing to restore flight target through a changed parent: {}\",\n            entry.target.display()\n        );\n    }\n    Ok(())\n}\n\npub(super) fn 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":523,"sourceCodeEnd":559,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/flight.rs#L523-L559","documentation":"Before restoring a flight backup (rollback or crash recovery), mise re-resolves the parent directories of both the target and the backup and compares them to the parents recorded when the transaction started. If either parent changed (renamed, replaced by a symlink, on a different volume after mount changes), mise refuses to restore, because renaming through the changed parent could place the original in the wrong location or follow an attacker-controlled symlink.","triggerScenarios":"validate_backup_parents, called from rollback or recover_flight_backup, finds resolved_parent(target) != recorded target_parent, or the backup's resolved parent differs from recorded backup_parent, or backup/backup_parent presence is inconsistent (one set, other None).","commonSituations":"The user (or another installer) renamed/moved the app's containing directory between the failed install and the retry; a symlink swap on /Applications or a custom appdir; a network volume mount point changed; recovery attempted long after layout changed.","solutions":["Restore the original directory layout (recreate/rename the parent directory recorded in the recovery record) and retry the install.","Manually move the backup file from its backup location back to the intended target, then delete the .recovery record.","Remove unwanted symlinks in the parent path so parent resolution matches the recorded parent.","If the layout change is intentional, discard the backup and stale recovery record and reinstall the cask fresh."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// verify the target's parent still resolves where you expect before retrying recovery\nreadlink -f \"$(dirname \"$TARGET_PATH\")\"","typeGuard":null,"tryCatchPattern":"// recovery can legitimately fail while layout is mid-change; retry after fixing parents\nloop {\n    match recover_flight_backups() {\n        Err(e) if e.to_string().contains(\"changed parent\") => {\n            restore_original_parent_layout()?; // user action: undo rename/symlink swap\n            continue;\n        }\n        other => break other,\n    }\n}","preventionTips":["Don't rename or symlink-swap the app's containing directory between a failed install and its retry.","Keep appdir mounts (network volumes) attached until install/rollback completes.","If layout must change, discard the stale backup + recovery record and reinstall fresh."],"tags":["brew","recovery","path-validation","safety"],"backgroundTag":"invalid-state-transition","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}