{"record":{"id":"e53d0bc85912ad78","repo":"Hmbown/CodeWhale","slug":"saved-session-checkpoint-exceeds-its-transcript-restore-the","errorCode":null,"errorMessage":"Saved session checkpoint exceeds its transcript; restore the saved session before resuming","messagePattern":"Saved session checkpoint exceeds its transcript; restore the saved session before resuming","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":11315,"sourceCode":"                prefix.extend(session_recovery_projection(\n                    &self.reconstruct_messages_from_turns(std::slice::from_ref(turn))?,\n                ));\n                if prefix == expected {\n                    covered = Some(index + 1);\n                } else if !expected.starts_with(&prefix) {\n                    break;\n                }\n            }\n            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\"))?\n        };\n        let mut messages = session.messages;\n        if let Some(retained) = thread\n            .saved_session_checkpoint\n            .as_ref()\n            .and_then(|checkpoint| checkpoint.retained_messages)\n        {\n            if retained > messages.len() {\n                bail!(\n                    \"Saved session checkpoint exceeds its transcript; restore the saved session before resuming\"\n                );\n            }\n            messages.truncate(retained);\n        }\n        Ok(Some((messages, covered)))\n    }\n\n    fn reconstruct_messages_from_turns(&self, turns: &[TurnRecord]) -> Result<Vec<Message>> {\n        let mut messages = Vec::new();\n        for turn in turns {\n            let stored_items = self.store.list_items_for_turn(&turn.id)?;\n            let items = if turn.item_ids.is_empty() {\n                stored_items\n            } else {\n                let mut by_id: HashMap<String, TurnItemRecord> = stored_items\n                    .iter()\n                    .cloned()","sourceCodeStart":11297,"sourceCodeEnd":11333,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L11297-L11333","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (checkpoint expects 50 messages, transcript has 30)\nruntime.resume_thread(&thread, \"sess-1\").await?;\n// after\nlet restored = runtime.restore_session(\"sess-1\").await?;\nruntime.resume_thread(&restored.into_thread(), \"sess-1\").await?;","handlingStrategy":"validation","validationCode":"if let Some(cp) = &thread.saved_session_checkpoint {\n    if cp.retained_messages.map_or(false, |r| r > transcript_len) {\n        return Err(anyhow!(\"checkpoint exceeds transcript; restore session first\"));\n    }\n}","typeGuard":null,"tryCatchPattern":"match runtime.resume_thread(&thread, id).await {\n    Err(e) if e.to_string().contains(\"checkpoint exceeds its transcript\") => {\n        let restored = runtime.restore_session(id).await?;\n        runtime.resume_thread(&restored.into_thread(), id).await?;\n    }\n    other => other?,\n}","preventionTips":["Avoid truncating transcripts of threads with active checkpoints","Recompute the checkpoint when transcript length changes","Keep session files intact until threads are retired"],"tags":["state","checkpoint","data-integrity"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}