{"record":{"id":"f3f6f81efc8335ad","repo":"jdx/mise","slug":"brew-cask-unresolved-recovery-for-still-preser-f3f6f8","errorCode":null,"errorMessage":"brew-cask: unresolved recovery for {} still preserves its original at {}","messagePattern":"brew-cask: unresolved recovery for (.+?) still preserves its original at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/flight.rs","lineNumber":392,"sourceCode":"        return Ok(());\n    };\n    for entry in entries.flatten() {\n        let path = entry.path();\n        if path\n            .extension()\n            .is_none_or(|extension| extension != \"recovery\")\n        {\n            continue;\n        }\n        let Ok(body) = file::read_to_string(&path) else {\n            continue;\n        };\n        let Ok(record) = serde_json::from_str::<FlightRecoveryRecord>(&body) else {\n            continue;\n        };\n        if record.target == target {\n            if let Some(backup) = record.backup {\n                bail!(\n                    \"brew-cask: unresolved recovery for {} still preserves its original at {}\",\n                    target.display(),\n                    backup.display()\n                );\n            }\n            bail!(\n                \"brew-cask: unresolved recovery for newly created target {}\",\n                target.display()\n            );\n        }\n    }\n    Ok(())\n}\n\npub(super) fn recover_flight_backups() -> Result<()> {\n    let root = flight_recovery_root();\n    recover_flight_backups_in(&root)\n}","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/flight.rs#L374-L410","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the cask install — recover_flight_backups will attempt to restore the backup and clear the record on a healthy filesystem.","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.","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.","Check parent-directory permissions so the automatic recovery rename can succeed on the next run."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// on this error, attempt recovery then retry once\nmatch install_cask(token) {\n    Err(e) if e.to_string().contains(\"unresolved recovery\") => {\n        // re-running triggers recover_flight_backups; manual fallback: restore\n        // the backup path printed in the message, then delete the .recovery file\n        retry(install_cask(token))\n    }\n    other => other,\n}","preventionTips":["Don't kill mise (SIGKILL) during cask install/upgrade; let it finish or Ctrl-C cleanly.","Re-run the install promptly after an interruption so automatic recovery can clear records.","Avoid concurrent mise cask operations on the same app."],"tags":["brew","recovery","interrupted-transaction","state"],"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-14T00:17:10.932Z"}