{"record":{"id":"cd3256b97895bb03","repo":"jdx/mise","slug":"these-paths-keep-changing-while-being-protected-n","errorCode":null,"errorMessage":"these paths keep changing while being protected; nothing was changed: {}","messagePattern":"these paths keep changing while being protected; nothing was changed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/replay.rs","lineNumber":533,"sourceCode":"            .checkpoint\n            .tree\n            .snapshot\n            .clone()\n            .ok_or_else(|| eyre::eyre!(\"the protective checkpoint has no content\"))?;\n        let mut missing = vec![];\n        for step in steps.iter().filter(|step| step.action.mutates()) {\n            let Some(current) = repo.restored_object_at(&live, &step.tree_path)? else {\n                continue;\n            };\n            if repo.restored_object_at(&before_tree, &step.tree_path)? != Some(current) {\n                missing.push(step.path.clone());\n            }\n        }\n        if missing.is_empty() {\n            break;\n        }\n        if round >= VERIFY_ROUNDS {\n            bail!(\n                \"these paths keep changing while being protected; nothing was changed: {}\",\n                missing\n                    .iter()\n                    .map(display_path)\n                    .collect::<Vec<_>>()\n                    .join(\", \")\n            );\n        }\n        warn!(\n            \"history: {} path(s) changed after the protective checkpoint; capturing again\",\n            missing.len()\n        );\n        scope.recapture_before(&missing)?;\n    }\n    let mut touched = vec![];\n    // deletions deepest first, then writes shallowest first: a directory is\n    // emptied before the file that replaces it is written, and a directory\n    // that replaces a file exists before its files are written","sourceCodeStart":515,"sourceCodeEnd":551,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/replay.rs#L515-L551","documentation":"During protection, replay verifies captured paths across multiple rounds (VERIFY_ROUNDS). If paths are still 'missing' from the capture after the last round, the tree is changing underneath mise, so it aborts before applying anything.","triggerScenarios":"Paths that must be protected keep appearing/disappearing during the verify rounds — files are created, deleted, or renamed faster than the capture+verify loop can converge within VERIFY_ROUNDS iterations.","commonSituations":"A dev server, watcher, build, or test suite actively writing into the project while a history replay runs; flaky sync tools (Dropbox/OneDrive) churning files.","solutions":["Stop active writers (dev servers, watchers, sync clients) and re-run the replay","Identify and exclude the churning paths listed in the message","Retry when the working tree is quiet"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// before replay, check tree stability: hash the tree twice with a delay\nlet a = tree_fingerprint(root);\nstd::thread::sleep(Duration::from_secs(1));\nlet b = tree_fingerprint(root);\nassert_eq!(a, b, \"working tree is churning; stop writers first\");","typeGuard":null,"tryCatchPattern":"// retry after quiescing writers\nfor _ in 0..3 {\n    match apply_steps(...) {\n        Err(e) if e.to_string().contains(\"keep changing while being protected\") => {\n            stop_watchers();\n            continue;\n        }\n        r => break r,\n    }\n}","preventionTips":["Stop dev servers, file watchers, and sync clients during replays","Exclude noisy generated dirs (caches, logs) from tracked paths","Run replays when no other process writes to the project"],"tags":["history","replay","race-condition","concurrent-writes"],"backgroundTag":"concurrent-modification-detected","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}