{"record":{"id":"e1fd6ce9b9a95d10","repo":"jdx/mise","slug":"target-changed-after-unapply-planning","errorCode":null,"errorMessage":"target changed after unapply planning","messagePattern":"target changed after unapply planning","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/edits.rs","lineNumber":968,"sourceCode":"}\n\n/// Ensure template functions or another concurrent actor did not invalidate\n/// any edit ownership checks performed during planning.\npub(crate) fn validate_unapply(todo: &[UnapplyPlan<'_>]) -> Result<()> {\n    let mut checked = indexmap::IndexSet::new();\n    let mut problems = vec![];\n    for plan in todo {\n        if !checked.insert(plan.req.path.clone()) {\n            continue;\n        }\n        let result = if plan.req.path.is_symlink() {\n            Err(eyre::eyre!(\"{SYMLINK_REASON}\"))\n        } else {\n            file::read_to_string(&plan.req.path).and_then(|text| {\n                if text == plan.text {\n                    Ok(())\n                } else {\n                    bail!(\"target changed after unapply planning\")\n                }\n            })\n        };\n        if let Err(err) = result {\n            problems.push(format!(\n                \"  \\\"{}\\\" ({}): {err}\",\n                plan.req.path_raw,\n                plan.req.describe_op()\n            ));\n        }\n    }\n    if !problems.is_empty() {\n        bail!(\n            \"edits: cannot unapply these entries:\\n{}\",\n            problems.join(\"\\n\")\n        );\n    }\n    Ok(())","sourceCodeStart":950,"sourceCodeEnd":986,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/edits.rs#L950-L986","documentation":"Thrown by `validate_unapply` after the unapply plan was built: it re-reads each planned target file and compares its current text with the text captured at planning time (`plan.text`). If they differ, the file changed between planning and validation, so applying the plan could corrupt unintended edits; the error aborts the whole unapply and is aggregated into the problems list.","triggerScenarios":"Calling the unapply flow where the target file's contents change between plan_unapply (which snapshots file text) and validate_unapply (which re-reads it) — e.g. another process writes to the file in that window, or the path is a symlink swapped in between the two steps (symlinked paths bail earlier with SYMLINK_REASON).","commonSituations":"A file watcher, formatter, or editor auto-saved the file while the unapply command was running; a build tool regenerated config files concurrently; two instances of the tool ran at once on the same machine.","solutions":["Re-run the unapply command; the newly built plan will match the current file state.","Stop or pause concurrent processes (formatters, watchers, sync tools) that write to the target files while unapplying.","Ensure the path is a regular file, not a symlink, since symlinked targets are not supported for unapply.","Verify no other instance of the tool is operating on the same files concurrently."],"exampleFix":"// before: running unapply while a watcher rewrites config\n$ tool edits unapply &\n$ formatter --watch  # rewrites file mid-plan\n// after: stop the watcher first\n$ formatter stop && tool edits unapply","handlingStrategy":"retry","validationCode":"let before = fs::read_to_string(&path)?;\n// ... plan ...\nif fs::read_to_string(&path)? != before {\n    eprintln!(\"{} changed during planning; aborting\", path.display());\n}","typeGuard":null,"tryCatchPattern":"for attempt in 0..3 {\n    match tool.unapply() {\n        Err(e) if e.to_string().contains(\"target changed after unapply planning\") => continue,\n        other => { other?; break; }\n    }\n}","preventionTips":["Stop file watchers, formatters, and sync tools during unapply.","Run unapply serially; never run two instances concurrently.","Do not edit target files while the command is running."],"tags":["concurrency","race-condition","file-editing"],"backgroundTag":"invalid-state-transition","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}