jdx/mise · error

{} appeared after {} was protected; nothing more was changed

Error message

{} appeared after {} was protected; nothing more was changed

What it means

When replaying a step that recreates a directory, replay checks the directory's current contents against plan steps. A 'stray' file present inside the directory that is not covered by any step means it appeared after the directory was protected — replay aborts rather than overwrite or orphan it.

Source

Thrown at src/system/history/replay.rs:630

        let expected = repo.restored_object_at(&live, &step.tree_path)?;
        if !same_object(&expected, &current_object(repo, &step.path)?) {
            bail!(
                "{} changed after it was protected; nothing more was changed",
                display_path(&step.path)
            );
        }
        let was_directory = step.path.is_dir() && !step.path.is_symlink();
        let mut empty_dirs = vec![];
        if was_directory && !matches!(&step.action, Action::Write { mode, .. } if mode == "040000")
        {
            // replacing or removing a directory removes everything inside
            // it: a file history would capture must be a step of this plan
            // (else it appeared after protection); a file history never
            // covers goes with it, said out loud; empty subdirectories
            // leave no trace in a snapshot and are recorded for undo
            let inside = directory_contents(&step.path, steps, tracked)?;
            if let Some(stray) = inside.appeared.first() {
                bail!(
                    "{} appeared after {} was protected; nothing more was changed",
                    display_path(stray),
                    display_path(&step.path)
                );
            }
            if !inside.uncovered.is_empty() {
                // only reachable with --force (a type change): say what
                // goes with the directory that no checkpoint holds
                warn!(
                    "history: {} contains files history does not cover; they are removed with it and cannot be undone: {}",
                    display_path(&step.path),
                    inside
                        .uncovered
                        .iter()
                        .map(display_path)
                        .collect::<Vec<_>>()
                        .join(", ")
                );

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Remove or account for the stray file (it's named in the message), then re-run the replay
  2. Stop the process creating files inside the target directory and retry
  3. Re-plan so the new file is captured in a fresh protective checkpoint
Defensive patterns

Strategy: validation

Validate before calling

// ensure the directory to restore contains no untracked newcomers
let stray: Vec<_> = std::fs::read_dir(dir)?
    .filter_map(|e| e.ok())
    .filter(|e| !planned_paths.contains(&e.path()))
    .collect();
assert!(stray.is_empty(), "unplanned files present: {:?}", stray);

Prevention

When it happens

Trigger: directory_contents() finds inside.appeared non-empty: a file exists inside step.path now that was absent from the protective snapshot and has no corresponding plan step.

Common situations: A build/cache directory gets a new file (log, artifact, .DS_Store) between checkpoint and replay; watchers or sync clients drop files into restored directories mid-run.

Related errors


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