jdx/mise · error

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

Error message

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

What it means

validate_backup_parents runs during crash recovery of flight backups. It re-resolves the parent directory of the recorded target — and of the backup, if any — and compares against the parents recorded when the backup was taken (resolved_parent on both sides). A mismatch means the directory now reachable at that path is not the one the backup was made in (replaced, renamed, or reached through a different symlink), so restoring through it is refused.

Solutions

  1. Restore the parent directory to its original identity — remove replacement symlinks or move the real directory back — then re-run
  2. If the original parent is gone for good, restore the backup file by hand (cp/mv to the desired location) and delete the .recovery record
  3. Re-run the brew command after the parent path is stable
  4. Avoid renaming the appdir or prefix while recovery records are pending

Example fix

# before: parent replaced by a symlink
$ ls -ld ~/Applications   -> link to /Volumes/Extra/Applications

# after
$ rm ~/Applications && mkdir ~/Applications
$ mise brew install --cask foo   # recovery proceeds
Defensive patterns

Strategy: retry

Validate before calling

// Before recovery, confirm recorded parents still resolve identically.
fn parent_still_matches(record: &FlightRecoveryRecord) -> bool {
    std::fs::canonicalize(record.target.parent().unwrap_or(Path::new("/")))
        .map(|p| p == record.target_parent)
        .unwrap_or(false)
        && record.backup_parent.as_ref().map(|expected| {
            std::fs::canonicalize(record.backup.as_deref().unwrap().parent().unwrap())
                .map(|p| p == *expected).unwrap_or(false)
        }).unwrap_or(true)
}

Try / catch

match recover() {
    Err(e) if e.to_string().contains("changed parent") => {
        // restore the original parent directory identity, then retry recovery
        restore_parent_identity();
        recover()
    }
    other => other,
}

Prevention

When it happens

Trigger: The target's parent directory was unlinked/recreated (different inode) or a symlinked ancestor such as the appdir or Homebrew prefix was retargeted between the interrupted operation and the recovery run; validate_backup_parents then fails for that ArtifactLinkBackup entry.

Common situations: App directory moved or restructured after a crash; switching between /Applications and ~/Applications; prefix symlink changes; overlay/container filesystems remounting parent directories.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/f29e20b7baea0ee3. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask.rs:3447

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))
}

fn validate_backup_parents(entry: &ArtifactLinkBackup) -> Result<()> {
    if resolved_parent(&entry.target)? != entry.target_parent
        || entry
            .backup
            .as_deref()
            .zip(entry.backup_parent.as_ref())
            .is_some_and(|(backup, expected)| {
                !resolved_parent(backup).is_ok_and(|current| current == *expected)
            })
    {
        bail!(
            "brew-cask: refusing to restore flight target through a changed parent: {}",
            entry.target.display()
        );
    }
    Ok(())
}

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 6f52dcdf99)