jdx/mise · error

brew-cask: unresolved recovery for {} still preserves its or

Error message

brew-cask: unresolved recovery for {} still preserves its original at {}

What it means

App 'flight' (linking into /Applications etc.) writes a recovery record before moving a target's original aside, and deletes the record once the transaction resolves. Before starting a new protected operation on the same target, mise scans the recovery directory; if a record for this target still exists and names a backup of the original file, mise refuses to proceed, because an earlier run crashed mid-transaction and the original is preserved at the backup path awaiting manual/automatic recovery.

Source

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

        return Ok(());
    };
    for entry in entries.flatten() {
        let path = entry.path();
        if path
            .extension()
            .is_none_or(|extension| extension != "recovery")
        {
            continue;
        }
        let Ok(body) = file::read_to_string(&path) else {
            continue;
        };
        let Ok(record) = serde_json::from_str::<FlightRecoveryRecord>(&body) else {
            continue;
        };
        if record.target == target {
            if let Some(backup) = record.backup {
                bail!(
                    "brew-cask: unresolved recovery for {} still preserves its original at {}",
                    target.display(),
                    backup.display()
                );
            }
            bail!(
                "brew-cask: unresolved recovery for newly created target {}",
                target.display()
            );
        }
    }
    Ok(())
}

pub(super) fn recover_flight_backups() -> Result<()> {
    let root = flight_recovery_root();
    recover_flight_backups_in(&root)
}

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Re-run the cask install — recover_flight_backups will attempt to restore the backup and clear the record on a healthy filesystem.
  2. Manually restore: move the backup shown in the message back to the target path, then delete the stale .recovery file under the mise state brew-cask/flight-recovery directory.
  3. If the backup and target both exist and you know the target is the newer good copy, remove the backup and its recovery record manually.
  4. Check parent-directory permissions so the automatic recovery rename can succeed on the next run.
Defensive patterns

Strategy: try-catch

Try / catch

// on this error, attempt recovery then retry once
match install_cask(token) {
    Err(e) if e.to_string().contains("unresolved recovery") => {
        // re-running triggers recover_flight_backups; manual fallback: restore
        // the backup path printed in the message, then delete the .recovery file
        retry(install_cask(token))
    }
    other => other,
}

Prevention

When it happens

Trigger: protect_with_elevation → ensure_no_unresolved_flight_recovery finds a *.recovery record whose FlightRecoveryRecord.target equals the target and whose backup field is set — i.e. a previous install/upgrade crashed or was killed between staging the backup and commit/recovery.

Common situations: A previous mise run interrupted by kill/power loss during a cask app-link transaction; recovery failed earlier (e.g. parent directory changed) leaving the record on disk; the same app being installed concurrently from two runs.

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/f3f6f81efc8335ad. Report an issue: GitHub.