jdx/mise · error

cannot protect

Error message

cannot protect {}: {reason}

What it means

During a preimage snapshot, snapshot walks paths and records their modes when they are eligible for protection. If a path resolves to PathSnapshot::Unrecorded with a reason (e.g. the path's state could not be captured), and the path is otherwise eligible, protecting history would be incomplete, so it aborts with 'cannot protect <path>: <reason>'. The bundled reason explains exactly why the snapshot could not record it.

Solutions

  1. Read the <reason> in the message and fix the underlying cause (permissions, file type, or file churn) for the named path
  2. Exclude the problematic path from history protection via the history ignore/exclusion configuration
  3. Re-run the operation after the path is stable; transient races (file replaced during snapshot) usually resolve on retry

Example fix

// before: snapshot fails on an unreadable path
// cannot protect /etc/secret.key: permission denied

// after: exclude the path from protection
// [history]
// ignore = ["/etc/secret.key"]
Defensive patterns

Strategy: try-catch

Validate before calling

# ensure paths are snapshot-able before enabling protection
for f in $WATCHED_PATHS; do
  [ -r "$f" ] || echo "warning: $f not readable; exclude it from history protection" >&2
done

Try / catch

match history::snapshot() {
    Err(e) if e.to_string().starts_with("cannot protect") => {
        // parse the path and reason, exclude the path, retry once
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling the history snapshot/preimage capture for a path that is eligible for protection but whose PathSnapshot came back Unrecorded { reason }, e.g. unreadable files, files that changed mid-snapshot, or unsupported file types.

Common situations: History protection enabled while the working tree contains files the snapshotter cannot record: permission-restricted files, sockets/fifos, rapidly changing files, or paths deleted between listing and snapshot.

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@533346cc37 (2026-09-17). Data as JSON: /api/errors/b2de4288ae878313. Report an issue: GitHub.

Appendix: source

Thrown at src/system/history/checkpoint/preimages.rs:244

                    for link in links {
                        self.snapshot(
                            &path.join(&link.rel),
                            &PathSnapshot::Symlink {
                                dest: link.dest.clone(),
                            },
                            tree,
                        )?;
                    }
                }
            }
            PathSnapshot::Directory { mode } => {
                if self.eligible(path)? {
                    self.mode(path, Some(*mode));
                }
            }
            PathSnapshot::Unrecorded { reason, .. } => {
                if self.eligible(path)? {
                    eyre::bail!("cannot protect {}: {reason}", display_path(path));
                }
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::system::files::{FileMode, FilePolicy};
    use crate::system::history::journal::Capture;

    #[test]
    fn first_preimages_survive_multiple_phases_without_saving_siblings() -> Result<()> {
        let state = tempfile::tempdir()?;
        let live =
            tempfile::tempdir_in(crate::system::history::sync::layout::Roots::current().home)?;

View on GitHub (pinned to 533346cc37)