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

  1. Fix the underlying capture problem reported in `{reason}` — usually restore read permissions or replace a broken symlink with a real file.
  2. Re-initialize the affected path from the repository so a valid preimage can be captured.
  3. Run outside the strict recovery scope (bootstrap mode) where the library only warns instead of failing.
  4. 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

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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/0358a2c4098adbd4. Report an issue: GitHub.