@@ -179,10 +179,18 @@ impl CortexSession {
179179 }
180180
181181 /// Updates token counts in metadata.
182+ ///
183+ /// Saves metadata to disk. If save fails, the in-memory state is still
184+ /// updated but marked as modified for later retry.
182185 pub fn add_tokens ( & mut self , input : i64 , output : i64 ) {
183186 self . meta . add_tokens ( input, output) ;
184187 if let Err ( e) = self . storage . save_meta ( & self . meta ) {
185- tracing:: error!( "Failed to save metadata: {}" , e) ;
188+ tracing:: error!(
189+ session_id = %self . meta. id,
190+ error = %e,
191+ "Failed to save metadata after token update"
192+ ) ;
193+ self . modified = true ;
186194 }
187195 }
188196
@@ -201,79 +209,135 @@ impl CortexSession {
201209
202210 /// Removes and returns the last exchange (user + assistant messages).
203211 /// Returns None if there are fewer than 2 messages.
212+ ///
213+ /// Only updates in-memory state after successful storage operations.
214+ /// If storage fails, the messages are restored to maintain consistency.
204215 pub fn pop_last_exchange ( & mut self ) -> Option < Vec < cortex_core:: widgets:: Message > > {
205216 if self . messages . len ( ) < 2 {
206217 return None ;
207218 }
208219
220+ // Pop messages from memory temporarily
221+ let last = self . messages . pop ( ) ;
222+ let prev = self . messages . pop ( ) ;
223+
224+ // Build result from popped messages
209225 let mut result = Vec :: new ( ) ;
226+ let mut popped_messages = Vec :: new ( ) ;
210227
211- // Pop the last message (should be assistant)
212- if let Some ( last) = self . messages . pop ( ) {
213- let role = match last. role . as_str ( ) {
228+ if let Some ( ref msg) = prev {
229+ let role = match msg. role . as_str ( ) {
214230 "assistant" => cortex_core:: widgets:: MessageRole :: Assistant ,
215231 "user" => cortex_core:: widgets:: MessageRole :: User ,
216232 _ => cortex_core:: widgets:: MessageRole :: System ,
217233 } ;
218234 result. push ( cortex_core:: widgets:: Message {
219235 role,
220- content : last . content ,
236+ content : msg . content . clone ( ) ,
221237 timestamp : None ,
222238 is_streaming : false ,
223239 tool_name : None ,
224240 } ) ;
241+ popped_messages. push ( msg. clone ( ) ) ;
225242 }
226243
227- // Pop the previous message (should be user)
228- if let Some ( prev) = self . messages . pop ( ) {
229- let role = match prev. role . as_str ( ) {
244+ if let Some ( ref msg) = last {
245+ let role = match msg. role . as_str ( ) {
230246 "assistant" => cortex_core:: widgets:: MessageRole :: Assistant ,
231247 "user" => cortex_core:: widgets:: MessageRole :: User ,
232248 _ => cortex_core:: widgets:: MessageRole :: System ,
233249 } ;
234- result. insert (
235- 0 ,
236- cortex_core:: widgets:: Message {
237- role,
238- content : prev. content ,
239- timestamp : None ,
240- is_streaming : false ,
241- tool_name : None ,
242- } ,
243- ) ;
250+ result. push ( cortex_core:: widgets:: Message {
251+ role,
252+ content : msg. content . clone ( ) ,
253+ timestamp : None ,
254+ is_streaming : false ,
255+ tool_name : None ,
256+ } ) ;
257+ popped_messages. push ( msg. clone ( ) ) ;
244258 }
245259
246- // Update metadata
247- self . meta . message_count = self . messages . len ( ) as u32 ;
248- self . modified = true ;
260+ // Try to save updated state to storage
261+ let rewrite_result = self . storage . rewrite_messages ( & self . meta . id , & self . messages ) ;
262+
263+ match rewrite_result {
264+ Ok ( ( ) ) => {
265+ // Storage succeeded, update metadata
266+ self . meta . message_count = self . messages . len ( ) as u32 ;
267+
268+ if let Err ( e) = self . storage . save_meta ( & self . meta ) {
269+ tracing:: error!(
270+ session_id = %self . meta. id,
271+ error = %e,
272+ "Failed to save metadata after undo - history is updated but metadata may be stale"
273+ ) ;
274+ self . modified = true ;
275+ }
249276
250- // Save updated state (messages need to be rewritten)
251- if let Err ( e) = self . storage . rewrite_messages ( & self . meta . id , & self . messages ) {
252- tracing:: error!( "Failed to rewrite messages after undo: {}" , e) ;
253- }
254- if let Err ( e) = self . storage . save_meta ( & self . meta ) {
255- tracing:: error!( "Failed to save metadata after undo: {}" , e) ;
256- }
277+ Some ( result)
278+ }
279+ Err ( e) => {
280+ // Storage failed - restore messages to maintain consistency
281+ tracing:: error!(
282+ session_id = %self . meta. id,
283+ error = %e,
284+ "Failed to rewrite messages after undo - restoring original state"
285+ ) ;
286+
287+ // Restore in reverse order (prev was popped second, so push it first)
288+ if let Some ( msg) = prev {
289+ self . messages . push ( msg) ;
290+ }
291+ if let Some ( msg) = last {
292+ self . messages . push ( msg) ;
293+ }
257294
258- Some ( result)
295+ // Return None to indicate the operation failed
296+ None
297+ }
298+ }
259299 }
260300
261301 /// Internal method to add a message and persist it.
302+ ///
303+ /// Only updates in-memory state after successful storage operations.
304+ /// This ensures consistency between disk and memory state.
262305 fn add_message_internal ( & mut self , message : StoredMessage ) -> & StoredMessage {
263- // Append to storage first
264- if let Err ( e) = self . storage . append_message ( & self . meta . id , & message) {
265- tracing:: error!( "Failed to save message: {}" , e) ;
266- }
306+ // Try to append to storage first - only update memory state on success
307+ match self . storage . append_message ( & self . meta . id , & message) {
308+ Ok ( ( ) ) => {
309+ // Storage succeeded, now update metadata
310+ self . meta . increment_messages ( ) ;
311+
312+ // Try to save metadata - if this fails, we still keep the message
313+ // since it was already persisted to history
314+ if let Err ( e) = self . storage . save_meta ( & self . meta ) {
315+ tracing:: error!(
316+ session_id = %self . meta. id,
317+ error = %e,
318+ "Failed to save metadata after message append - message is saved but metadata may be stale"
319+ ) ;
320+ }
267321
268- // Update metadata
269- self . meta . increment_messages ( ) ;
270- if let Err ( e) = self . storage . save_meta ( & self . meta ) {
271- tracing:: error!( "Failed to save metadata: {}" , e) ;
322+ // Add to in-memory list only after storage success
323+ self . messages . push ( message) ;
324+ }
325+ Err ( e) => {
326+ // Storage failed - log error but still add to memory for this session
327+ // This allows the conversation to continue even if persistence fails
328+ tracing:: error!(
329+ session_id = %self . meta. id,
330+ error = %e,
331+ "Failed to save message to storage - message exists only in memory"
332+ ) ;
333+
334+ // Still add to memory so the conversation can continue
335+ self . messages . push ( message) ;
336+ self . modified = true ; // Mark as modified since we have unsaved changes
337+ }
272338 }
273339
274- // Add to in-memory list
275- self . messages . push ( message) ;
276- self . messages . last ( ) . unwrap ( )
340+ self . messages . last ( ) . expect ( "message was just added" )
277341 }
278342
279343 /// Converts messages to API format for completion requests.
0 commit comments