jdx/mise · error
cannot safely change {} without a recovery preimage: {reason
Error message
cannot safely change {} without a recovery preimage: {reason} What it means
Thrown by `journal::begin_changes_with` (via `begin_changes`) when a path scheduled for modification could not be captured as a recovery preimage (`PathSnapshot::Unrecorded`) and the current scope `requires_recovery_preimage()`. The journal needs a 'before' copy to enable rollback; without it, changing the file would be unrecoverable, so the library refuses to proceed.
Source
Thrown at src/system/history/journal.rs:534
}
/// [`begin_changes`] with a capture depth per path. Fails when an entry
/// cannot be persisted: the mutation must not go ahead without its
/// write-ahead record.
pub(crate) fn begin_changes_with(
part: &str,
item: &str,
paths: impl IntoIterator<Item = (PathBuf, Capture)>,
) -> Result<Vec<PendingChange>> {
if !super::scope::is_active() {
return Ok(vec![]);
}
let mut pending = vec![];
for (path, capture) in paths {
let prior = PathSnapshot::capture_with(&dirs::STATE, &path, capture);
if let PathSnapshot::Unrecorded { reason, .. } = &prior {
if super::scope::requires_recovery_preimage() {
eyre::bail!(
"cannot safely change {} without a recovery preimage: {reason}",
display_path(&path)
);
}
warn!(
"bootstrap: temporary recovery is unavailable for {}: {reason}; proceeding with deployment",
display_path(&path)
);
}
if let Some(seq) = super::scope::record(JournalEntry::PathChanged {
part: part.to_string(),
item: item.to_string(),
path: path.clone(),
prior,
})? {
pending.push(PendingChange { seq, path });
}
}View on GitHub (pinned to afd2eddd3a)
Solutions
- Fix the underlying capture problem reported in `{reason}` — usually restore read permissions or replace a broken symlink with a real file.
- Re-initialize the affected path from the repository so a valid preimage can be captured.
- Run outside the strict recovery scope (bootstrap mode) where the library only warns instead of failing.
- Inspect the state directory for missing/corrupt snapshot data and repair or clear it.
Defensive patterns
Strategy: validation
Validate before calling
// before the operation: test -r "$FILE" && [ ! -L "$FILE" ] || echo "cannot snapshot $FILE"
Try / catch
// catch "cannot safely change ... without a recovery preimage" // -> repair the path (permissions/symlink) then retry, or re-run in bootstrap scope
Prevention
- Keep managed files real, readable files — not broken symlinks or special files.
- Don't manually chmod managed dotfiles to unreadable modes.
- Let the tool complete journal recovery after interruptions instead of deleting state.
When it happens
Trigger: Beginning a journaled change to a file that cannot be snapshotted — unreadable file, symlink/special-file the capture backend doesn't support, or a path outside the managed tree — while inside a strict scope that demands recovery preimages.
Common situations: Corrupted permissions on a managed dotfile after a restore; files replaced by symlinks by another tool; state directory partially deleted; running with a strict recovery scope after a previous interrupted journal.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- recovery directory descends through a file or symlink
- no usable preimage: {reason}
- brew-cask: unresolved recovery for {} still preserves its or
- brew-cask: unresolved recovery for newly created target {}
- brew-cask: refusing to restore flight target through a chang
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/0358a2c4098adbd4.
Report an issue: GitHub.