{"record":{"id":"cea69320f088a0ad","repo":"jdx/mise","slug":"checkpoint-has-no-protective-checkpoint-to-undo","errorCode":null,"errorMessage":"checkpoint {} has no protective checkpoint to undo from","messagePattern":"checkpoint (.+?) has no protective checkpoint to undo from","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/replay.rs","lineNumber":289,"sourceCode":"    let Some(op) = operation.checkpoint.operation.clone() else {\n        bail!(\"checkpoint {} is not an operation\", operation.id);\n    };\n    // Undo restores tracked files only, never package or service state.\n    if op.status == OperationStatus::Pending {\n        bail!(\n            \"checkpoint {} is still running or was interrupted; nothing to undo\",\n            operation.id\n        );\n    }\n    if op.status == OperationStatus::Failed && !op.affected.is_empty() {\n        info!(\n            \"history: operation {} failed midway; reversing the {} path(s) it changed\",\n            operation.id,\n            op.affected.len()\n        );\n    }\n    let Some(before_uuid) = &op.before else {\n        bail!(\n            \"checkpoint {} has no protective checkpoint to undo from\",\n            operation.id\n        );\n    };\n    let Some(before) = entries\n        .iter()\n        .find(|entry| &entry.checkpoint.uuid == before_uuid)\n        .cloned()\n    else {\n        bail!(\n            \"checkpoint {} is unavailable; cannot find the state before operation {}\",\n            before_uuid,\n            operation.id\n        );\n    };\n    if op.affected.is_empty() {\n        info!(\"history: operation {} touched nothing\", operation.id);\n        return Ok(());","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/replay.rs#L271-L307","documentation":"Every undoable operation records a protective `before` checkpoint UUID capturing the state prior to the operation. Undo restores from that checkpoint; if op.before is None the protective snapshot was never recorded, so there is no state to restore to and mise bails.","triggerScenarios":"Running undo against an operation whose op.before is None — the operation was created without a pre-operation snapshot (older format, snapshot skipped, or corrupted record).","commonSituations":"History entries created by an older mise version before protective checkpoints were recorded; manually edited/corrupted history store; operations that bypassed checkpoint creation.","solutions":["Use rollback with explicit paths or `--to <ref> --all` to restore files from a known checkpoint instead","Check `mise bootstrap dotfiles history` for checkpoints that do have before-state","If the store is corrupted, re-initialize dotfiles tracking and re-snapshot"],"exampleFix":"// before\nmise bootstrap dotfiles undo   # operation lacks before-checkpoint\n// after\nmise bootstrap dotfiles rollback --to <known-ref> --all","handlingStrategy":"validation","validationCode":"const entry = await getCheckpoint(id);\nif (entry?.checkpoint?.operation && !entry.checkpoint.operation.before) {\n  throw new Error(`checkpoint ${id} has no protective before-checkpoint`);\n}","typeGuard":"const hasBefore = (op: Operation): op is Operation & { before: string } =>\n  typeof op.before === \"string\" && op.before.length > 0;","tryCatchPattern":"try {\n  await undo();\n} catch (e) {\n  if (String(e).includes(\"no protective checkpoint\")) {\n    await rollback({ paths: knownPaths });\n  } else throw e;\n}","preventionTips":["Ensure operations always create the pre-operation snapshot","Migrate history created by older versions lacking before refs","Fall back to rollback when before-state is absent"],"tags":["undo","checkpoint","missing-data"],"backgroundTag":"resource-not-found","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}