{"record":{"id":"50555f330459bf9d","repo":"jdx/mise","slug":"changed-after-the-operation-left-untouched","errorCode":null,"errorMessage":"changed after the operation; left untouched","messagePattern":"changed after the operation; left untouched","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/system/history/recovery.rs","lineNumber":86,"sourceCode":"    after: Option<&PathState>,\n) -> Result<()> {\n    validate_destination(path)?;\n    let comparison = tempfile::tempdir()?;\n    let depth = if matches!(prior, PathSnapshot::Directory { .. }) {\n        Capture::Shallow\n    } else {\n        Capture::Full\n    };\n    // Comparing must not leave copies of concurrent user edits in the store.\n    let current = PathSnapshot::capture_with(comparison.path(), path, depth);\n    if !matches!(prior, PathSnapshot::Unrecorded { .. }) && &current == prior {\n        return Ok(());\n    }\n    let Some(after) = after else {\n        bail!(\"write completion was not recorded; inspect the live file before retrying recovery\");\n    };\n    if PathState::observe(path) != *after {\n        bail!(\"changed after the operation; left untouched\");\n    }\n    // Entry count alone cannot establish a directory's identity. Never\n    // replace a populated directory on that evidence.\n    if matches!(after, PathState::Dir { entries, .. } if *entries != 0)\n        && !(matches!(prior, PathSnapshot::Directory { .. })\n            && matches!(\n                after,\n                PathState::Dir {\n                    identity: Some(_),\n                    ..\n                }\n            ))\n    {\n        bail!(\"directory contents cannot be verified safely; left untouched\");\n    }\n    validate_snapshot(state_dir, prior)?;\n    if PathState::observe(path) != *after {\n        bail!(\"changed while preparing recovery; left untouched\");","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/history/recovery.rs#L68-L104","documentation":"Before restoring, recover_path re-observes the live path and requires it to match the recorded post-write snapshot (`after`). If the file changed since the operation completed, restoring would clobber those newer edits, so mise leaves the path untouched and reports it.","triggerScenarios":"recover_path is called on a path whose current PathState differs from the stored `after` snapshot — the file was edited (by the user, an editor, another tool, or a sync process) between the original write and the recovery attempt.","commonSituations":"User edited the dotfile after the interrupted operation before running recover; a dotfiles sync tool (e.g. another machine's push) rewrote the file; the shell rc file was regenerated by another program between crash and recovery.","solutions":["Review the current file content and decide whether to keep it: `mise dot recover <operation> --keep-current`.","If the newer edit should be discarded, restore the intended content manually (or from the recovery store) and re-run recover.","Back up the current file (cp to a safe path) before retrying recovery so the newer edits are preserved somewhere.","Re-run `mise dot recover` immediately after resolving, since further changes will re-trigger this guard."],"exampleFix":"// before\n$ mise dot recover   # changed after the operation; left untouched\n// after\ncp ~/.zshrc ~/.zshrc.myedit.bak\nmise dot recover <op> --keep-current   # or restore intended content first","handlingStrategy":"validation","validationCode":"export function assertUnchangedSinceOperation(beforeHash: string, currentHash: string): void {\n  if (beforeHash !== currentHash) {\n    throw new Error('file changed since the operation; back up edits before recovery');\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await dotRecover(op);\n} catch (e) {\n  if (/changed after the operation/.test(String(e))) {\n    backupFile(path);                 // keep the newer edits\n    await dotRecover(op, { keepCurrent: true });\n  } else throw e;\n}","preventionTips":["Close editors and stop sync daemons before recovering","Back up dotfiles before running recovery","Run recover promptly after a crash, before other tools touch files"],"tags":["recovery","dotfiles","conflict","filesystem"],"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"}