jdx/mise · error

brew-cask: refusing to restore flight target through a chang

Error message

brew-cask: refusing to restore flight target through a changed parent: {}

What it means

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.

Source

Thrown at src/system/packages/brew/cask/flight.rs:541

}

pub(super) fn resolved_parent(path: &Path) -> Result<PathBuf> {
    let parent = path
        .parent()
        .ok_or_else(|| eyre!("brew-cask: flight target has no parent"))?;
    Ok(path_with_resolved_existing_ancestor(parent))
}

pub(super) fn validate_backup_parents(entry: &ArtifactLinkBackup) -> Result<()> {
    let backup_parent_matches = match (&entry.backup, &entry.backup_parent) {
        (Some(backup), Some(expected)) => {
            resolved_parent(backup).is_ok_and(|current| current == *expected)
        }
        (None, None) => true,
        _ => false,
    };
    if resolved_parent(&entry.target)? != entry.target_parent || !backup_parent_matches {
        bail!(
            "brew-cask: refusing to restore flight target through a changed parent: {}",
            entry.target.display()
        );
    }
    Ok(())
}

pub(super) fn flight_backup_parent(target: &Path) -> Result<&Path> {
    if let Some(app) = target.ancestors().find(|ancestor| {
        ancestor
            .extension()
            .is_some_and(|extension| extension.eq_ignore_ascii_case("app"))
    }) {
        return app
            .parent()
            .ok_or_else(|| eyre!("brew-cask: app flight target has no parent"));
    }
    target

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Restore the original directory layout (recreate/rename the parent directory recorded in the recovery record) and retry the install.
  2. Manually move the backup file from its backup location back to the intended target, then delete the .recovery record.
  3. Remove unwanted symlinks in the parent path so parent resolution matches the recorded parent.
  4. If the layout change is intentional, discard the backup and stale recovery record and reinstall the cask fresh.
Defensive patterns

Strategy: retry

Validate before calling

// verify the target's parent still resolves where you expect before retrying recovery
readlink -f "$(dirname "$TARGET_PATH")"

Try / catch

// recovery can legitimately fail while layout is mid-change; retry after fixing parents
loop {
    match recover_flight_backups() {
        Err(e) if e.to_string().contains("changed parent") => {
            restore_original_parent_layout()?; // user action: undo rename/symlink swap
            continue;
        }
        other => break other,
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/89688e17a6cb47fa. Report an issue: GitHub.