Hmbown/CodeWhale · error
Saved session checkpoint exceeds its transcript; restore…
Error message
Saved session checkpoint exceeds its transcript; restore the saved session before resuming
What it means
A thread's saved_session_checkpoint can pin how many transcript messages were retained at checkpoint time. If that retained count exceeds the number of messages actually present in the in-memory transcript, the checkpoint is inconsistent with the transcript and resuming would fabricate history, so the operation bails and asks you to restore the saved session first.
Solutions
- Restore the saved session (full transcript) and resume from that restored copy
- Clear or recompute the thread's saved_session_checkpoint so it matches the current transcript
- Re-import the session into a new thread instead of resuming the truncated one
Example fix
// before (checkpoint expects 50 messages, transcript has 30)
runtime.resume_thread(&thread, "sess-1").await?;
// after
let restored = runtime.restore_session("sess-1").await?;
runtime.resume_thread(&restored.into_thread(), "sess-1").await?; Defensive patterns
Strategy: validation
Validate before calling
if let Some(cp) = &thread.saved_session_checkpoint {
if cp.retained_messages.map_or(false, |r| r > transcript_len) {
return Err(anyhow!("checkpoint exceeds transcript; restore session first"));
}
} Try / catch
match runtime.resume_thread(&thread, id).await {
Err(e) if e.to_string().contains("checkpoint exceeds its transcript") => {
let restored = runtime.restore_session(id).await?;
runtime.resume_thread(&restored.into_thread(), id).await?;
}
other => other?,
} Prevention
- Avoid truncating transcripts of threads with active checkpoints
- Recompute the checkpoint when transcript length changes
- Keep session files intact until threads are retired
When it happens
Trigger: Resuming a thread whose saved_session_checkpoint.retained_messages is greater than messages.len() — e.g. the transcript was truncated, cleared, or a different/older transcript was loaded before resume.
Common situations: Manually editing or pruning a session transcript; loading a partial session file; a crash or migration that dropped turns from the in-memory thread while the checkpoint still references the full count.
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
- Invalid pet score checkpoint.
- Invalid pet score checkpoint.
- Invalid pet state.
- 1
- A cancelled Operation cannot be edited.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/e53d0bc85912ad78.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:11315
prefix.extend(session_recovery_projection(
&self.reconstruct_messages_from_turns(std::slice::from_ref(turn))?,
));
if prefix == expected {
covered = Some(index + 1);
} else if !expected.starts_with(&prefix) {
break;
}
}
covered.with_context(|| format!("Saved session {session_id} has no verifiable Runtime checkpoint; keep both histories and re-import the saved session into a separate thread"))?
};
let mut messages = session.messages;
if let Some(retained) = thread
.saved_session_checkpoint
.as_ref()
.and_then(|checkpoint| checkpoint.retained_messages)
{
if retained > messages.len() {
bail!(
"Saved session checkpoint exceeds its transcript; restore the saved session before resuming"
);
}
messages.truncate(retained);
}
Ok(Some((messages, covered)))
}
fn reconstruct_messages_from_turns(&self, turns: &[TurnRecord]) -> Result<Vec<Message>> {
let mut messages = Vec::new();
for turn in turns {
let stored_items = self.store.list_items_for_turn(&turn.id)?;
let items = if turn.item_ids.is_empty() {
stored_items
} else {
let mut by_id: HashMap<String, TurnItemRecord> = stored_items
.iter()
.cloned()View on GitHub (pinned to 73e0f67d83)