jdx/mise · error

checkpoint did not finish; there is no state to roll back to

Error message

checkpoint {} did not finish; there is no state to roll back to

What it means

Rollback refuses checkpoints that are still marked Pending: their recorded state is not final, so rolling back to them would restore an indeterminate state. The checkpoint id is valid but unusable until the operation that created it completes.

Solutions

  1. Wait for the operation that created the checkpoint to finish, then retry the rollback.
  2. If the creating process is dead, remove/clean the stale checkpoint from the history store and use an earlier completed checkpoint.
  3. Use a different, completed checkpoint id as the rollback target.
Defensive patterns

Strategy: validation

Validate before calling

mise history list | jq 'select(.id == "<id>") | .status'  # must not be "pending"

Prevention

When it happens

Trigger: `mise history rollback <id>` where Entry.checkpoint.status() == Some(OperationStatus::Pending) in refuse_unusable.

Common situations: A previous mise operation (install, task run with checkpointing) crashed or is still running; stale Pending entries left after a killed process.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/system/history/replay.rs:1477

            return Ok(Some(entry.clone()));
        }
        // held content that differs, or a known absence while the path now
        // exists, are both versions to return to
        let differs = saved.is_some()
            || matches!(
                classify(&entry.checkpoint, &tree_path_to_display(&tree_path)),
                PathState::Absent
            );
        if differs {
            return Ok(Some(entry.clone()));
        }
    }
    Ok(None)
}

fn refuse_unusable(entry: &Entry) -> Result<()> {
    if entry.checkpoint.status() == Some(OperationStatus::Pending) {
        bail!(
            "checkpoint {} did not finish; there is no state to roll back to",
            entry.id
        );
    }
    if entry.checkpoint.tree.snapshot.is_none() {
        bail!("checkpoint {} has no content snapshot", entry.id);
    }
    if entry.checkpoint.schema_version > super::store::SCHEMA_VERSION {
        bail!(
            "checkpoint {} was written by a newer mise (schema {})",
            entry.id,
            entry.checkpoint.schema_version
        );
    }
    Ok(())
}

/// Every path a checkpoint's coverage includes, as absolute paths.

View on GitHub (pinned to 533346cc37)