jdx/mise · error

appeared after was protected; nothing more was changed

Error message

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

What it means

While restoring a directory, mise enumerates its live contents and checks that every file it finds is covered by a step in the plan. A 'stray' file means the directory gained a file after the checkpoint was taken; that file would not be captured by the plan or the undo record, so mise aborts the remaining steps (already-written steps are recorded and undoable).

Solutions

  1. Stop processes generating files inside the restored directory, then re-run the rollback.
  2. Move the stray files out of the restore directory if you want them preserved, then retry.
  3. Narrow the restore scope to individual files instead of the whole directory.
Defensive patterns

Strategy: validation

Validate before calling

find <dir> -newer <checkpoint-marker> -type f  # detect files created after protection

Prevention

When it happens

Trigger: directory_contents() reports an entry in inside.appeared during a directory restore step in apply_steps — a new file exists under step.path that no plan step accounts for.

Common situations: A watcher, package manager, or build task created files (logs, node_modules artifacts, caches) inside the directory between checkpoint and apply.

Related errors


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

Appendix: 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 533346cc37)