jdx/mise · error

cannot protect {}: {reason}

Error message

cannot protect {}: {reason}

What it means

When taking a history snapshot (`snapshot` in preimages.rs), each path resolves to a `PathSnapshot`. If a path is `Unrecorded` for some `reason` (e.g. unreadable, unsupported) but is still considered `eligible` for protection, mise cannot safely include it and bails with `cannot protect {path}: {reason}` instead of silently dropping a file it promised to protect.

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 afd2eddd3a)

Solutions

  1. Fix the underlying reason shown in the message (e.g. `chmod`/`chown` the file so it's readable by the current user).
  2. Exclude the problematic path from history tracking/config protection scope.
  3. Restore the missing file if it disappeared mid-snapshot, then retry the snapshot.

Example fix

# before: file unreadable
ls -l ~/.config/mise/secrets.toml  # -rw------- root root
# after
sudo chown $USER ~/.config/mise/secrets.toml && chmod u+rw ~/.config/mise/secrets.toml
Defensive patterns

Strategy: try-catch

Validate before calling

if !path.exists() || std::fs::metadata(&path).map(|m| m.permissions().readonly()).unwrap_or(true) {
    exclude_from_tracking(&path);
}

Try / catch

match result {
    Err(e) if e.to_string().starts_with("cannot protect") => fix_or_exclude(&path)?,
    other => other?,
}

Prevention

When it happens

Trigger: A path passes `eligible()` (so it's in the protection set) but its snapshot classification is `PathSnapshot::Unrecorded { reason, .. }` — e.g. unreadable file, permission issue, or unsupported file type discovered during snapshotting.

Common situations: A config/tracked file has restrictive permissions (mode 600 owned by another user), a file was deleted between listing and snapshotting, or a symlink/special file cannot be recorded.

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/b2de4288ae878313. Report an issue: GitHub.