{"record":{"id":"566a9200b4e70902","repo":"Hmbown/CodeWhale","slug":"saved-session-session-id-changed-after-this-thread-s","errorCode":null,"errorMessage":"Saved session {session_id} changed after this thread's checkpoint; re-import it into a separate thread to preserve both histories","messagePattern":"Saved session (.+?) changed after this thread's checkpoint; re-import it into a separate thread to preserve both histories","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":11276,"sourceCode":"        messages.extend(self.reconstruct_messages_from_turns(&turns[covered..])?);\n        Ok(messages)\n    }\n\n    fn saved_session_prefix(\n        &self,\n        thread: &ThreadRecord,\n        turns: &[TurnRecord],\n    ) -> Result<Option<(Vec<Message>, usize)>> {\n        let Some(session_id) = thread.session_id.as_deref() else {\n            return Ok(None);\n        };\n        let session = crate::session_manager::default_sessions_dir()\n            .and_then(crate::session_manager::SessionManager::new)\n            .and_then(|manager| manager.resume_session(session_id).map(|recovery| recovery.session))\n            .with_context(|| format!(\"Cannot read saved session {session_id}; restore that session file before resuming thread {}\", thread.id))?;\n        let covered = if let Some(checkpoint) = &thread.saved_session_checkpoint {\n            if checkpoint.messages_sha256 != session_messages_sha256(&session.messages)? {\n                bail!(\n                    \"Saved session {session_id} changed after this thread's checkpoint; re-import it into a separate thread to preserve both histories\"\n                );\n            }\n            match checkpoint.covered_turn_id.as_deref() {\n                Some(id) => turns.iter().position(|turn| turn.id == id)\n                    .map(|index| index + 1)\n                    .with_context(|| format!(\"Saved session checkpoint turn {id} is missing; restore the Runtime turn records before resuming\"))?,\n                None => 0,\n            }\n        } else {\n            // Legacy links have no cursor. Establish it only from an exact\n            // match to the existing seeder's projection; never use mtime or\n            // silently let a saved file replace a newer Runtime transcript.\n            let expected = session_recovery_projection(&session.messages);\n            let mut prefix = Vec::new();\n            let mut covered = (expected.is_empty()).then_some(0);\n            for (index, turn) in turns.iter().enumerate() {\n                if covered.is_some() {","sourceCodeStart":11258,"sourceCodeEnd":11294,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L11258-L11294","documentation":"When resuming a thread that imported a saved session, the code checkpoints a sha256 of the saved session's messages. If the on-disk session file has since changed, resuming here would silently merge two divergent histories, so it refuses and tells you to import the changed session into a separate thread.","triggerScenarios":"Calling the resume/import-thread API with a session_id whose saved session file was modified after the thread's saved_session_checkpoint was recorded (messages_sha256 mismatch).","commonSituations":"Editing or trimming a session JSONL while a thread referencing it is still open; another tool or another machine rewrote the session file; restoring a session from backup with different content.","solutions":["Restore the session file to the exact content captured at the checkpoint (undo edits or re-copy the original)","Import the changed session into a new thread instead of resuming the existing one, preserving both histories","Delete the checkpoint (start the thread fresh) only if the old history is not needed"],"exampleFix":"// before (fails because session file changed)\nruntime.resume_thread(&thread, \"sess-1\").await?;\n// after\nlet fresh = runtime.import_session_thread(\"sess-1\").await?; // separate thread keeps both histories","handlingStrategy":"validation","validationCode":"fn session_unchanged(checkpoint_sha: &str, session_path: &Path) -> anyhow::Result<bool> {\n    Ok(checkpoint_sha == sha256_hex(&std::fs::read(session_path)?))\n}","typeGuard":null,"tryCatchPattern":"match runtime.resume_thread(&thread, session_id).await {\n    Err(e) if e.to_string().contains(\"changed after this thread's checkpoint\") => {\n        let t = runtime.import_session_thread(session_id).await?;\n    }\n    other => other?,\n}","preventionTips":["Never edit a session file while a thread checkpoint references it","Compute and compare the messages sha256 before resuming","Import modified sessions into fresh threads"],"tags":["data-integrity","session","checksum"],"backgroundTag":"checksum-mismatch","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"}