{"record":{"id":"1d0f7cf6cd7d9a05","repo":"jdx/mise","slug":"brew-cask-unresolved-recovery-for-still-preser","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.rs","lineNumber":3300,"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\nfn recover_flight_backups() -> Result<()> {\n    let root = flight_recovery_root();\n    recover_flight_backups_in(&root)\n}","sourceCodeStart":3282,"sourceCodeEnd":3318,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L3282-L3318","documentation":"Before touching a target, ensure_no_unresolved_flight_recovery scans $STATE/brew-cask/flight-recovery/*.recovery records. Finding one whose target matches with a backup path set means a previous install/upgrade/uninstall was interrupted after moving the original file aside: that original is still preserved at the printed backup path and the transaction never committed. mise refuses the new operation rather than destroy the stranded original.","triggerScenarios":"A prior cask operation crashed, was Ctrl-C'd, or lost power mid-flight after the backup rename but before commit; the leftover .recovery JSON still names this target with a non-null backup field.","commonSituations":"Interrupted `mise` brew-cask installs or upgrades; CI jobs killed mid-run; laptop shutdown during install; anything that re-triggers the same target (reinstall/upgrade) afterward.","solutions":["Re-run the same mise brew command — pending recovery records are processed first and the operation then proceeds","Or restore manually: move the printed backup file back onto the target path, then delete the matching *.recovery record","If the target is disposable, delete both the target and the recovery record, then retry","Do not delete only the record while the backup file exists — that loses the original"],"exampleFix":"# before: stale recovery record\n$ mise brew install --cask foo   # refuses, names backup path\n\n# after: manual resolve then retry\n$ mv \"<backup-path-from-error>\" \"<target-path-from-error>\"\n$ rm \"<state>/brew-cask/flight-recovery/<hash>.recovery\"\n$ mise brew install --cask foo","handlingStrategy":"validation","validationCode":"// Before installing, check for pending recovery records naming your targets.\nfn pending_recovery_for(target: &Path) -> Option<PathBuf> {\n    let dir = state_dir().join(\"brew-cask/flight-recovery\");\n    std::fs::read_dir(dir).ok()?.flatten().map(|e| e.path())\n        .filter(|p| p.extension().is_some_and(|e| e == \"recovery\"))\n        .find(|p| std::fs::read_to_string(p).ok()\n            .and_then(|b| serde_json::from_str::<FlightRecoveryRecord>(&b).ok())\n            .is_some_and(|r| r.target == target && r.backup.is_some()))\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = install_cask(&cask) {\n    if let Some(msg) = e.to_string().strip_prefix(\"brew-cask: unresolved recovery for\") {\n        // surface the preserved-backup path to the user; do NOT auto-delete\n    }\n}","preventionTips":["Let mise finish or roll back — don't kill it mid-cask-operation","Resolve stale .recovery records (restore backup, delete record) before retrying","Never delete a recovery record while its backup file still exists"],"tags":["brew-cask","recovery","interrupted-install","transaction","state"],"backgroundTag":"leftover-recovery-state","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}