@@ -457,9 +457,7 @@ pub fn load_openai_chatgpt_session_with_mode(
457457}
458458
459459pub fn clear_openai_chatgpt_session ( ) -> Result < ( ) > {
460- let _ = clear_session_from_keyring ( ) ;
461- let _ = clear_session_from_file ( ) ;
462- Ok ( ( ) )
460+ clear_session_from_all_stores ( )
463461}
464462
465463pub fn clear_openai_chatgpt_session_with_mode ( mode : AuthCredentialsStoreMode ) -> Result < ( ) > {
@@ -540,7 +538,7 @@ async fn refresh_openai_chatgpt_session_without_lock(
540538 . context ( "failed to refresh openai chatgpt token" ) ?;
541539 response
542540 . error_for_status_ref ( )
543- . map_err ( |err| classify_refresh_error ( err , storage_mode ) ) ?;
541+ . map_err ( classify_refresh_error) ?;
544542 let token_response: OpenAITokenResponse = response
545543 . json ( )
546544 . await
@@ -745,22 +743,43 @@ async fn acquire_refresh_lock() -> Result<RefreshLockGuard> {
745743 Ok ( RefreshLockGuard { file } )
746744}
747745
748- fn classify_refresh_error (
749- err : reqwest:: Error ,
750- storage_mode : AuthCredentialsStoreMode ,
751- ) -> anyhow:: Error {
746+ fn classify_refresh_error ( err : reqwest:: Error ) -> anyhow:: Error {
752747 let status = err. status ( ) ;
753748 let message = err. to_string ( ) ;
754749 if status. is_some_and ( |status| status == reqwest:: StatusCode :: BAD_REQUEST )
755750 && ( message. contains ( "invalid_grant" ) || message. contains ( "refresh_token" ) )
756751 {
757- let _ = clear_openai_chatgpt_session_with_mode ( storage_mode) ;
752+ if let Err ( clear_err) = clear_session_from_all_stores ( ) {
753+ tracing:: warn!(
754+ "failed to clear expired openai chatgpt session across all stores: {clear_err}"
755+ ) ;
756+ }
758757 anyhow ! ( "Your ChatGPT session expired. Run `vtcode login openai` again." )
759758 } else {
760759 anyhow ! ( message)
761760 }
762761}
763762
763+ fn clear_session_from_all_stores ( ) -> Result < ( ) > {
764+ let mut errors = Vec :: new ( ) ;
765+
766+ if let Err ( err) = clear_session_from_keyring ( ) {
767+ errors. push ( err. to_string ( ) ) ;
768+ }
769+ if let Err ( err) = clear_session_from_file ( ) {
770+ errors. push ( err. to_string ( ) ) ;
771+ }
772+
773+ if errors. is_empty ( ) {
774+ Ok ( ( ) )
775+ } else {
776+ Err ( anyhow ! (
777+ "failed to clear openai session from all stores: {}" ,
778+ errors. join( "; " )
779+ ) )
780+ }
781+ }
782+
764783fn save_session_to_keyring ( serialized : & str ) -> Result < ( ) > {
765784 let entry = keyring:: Entry :: new ( OPENAI_STORAGE_SERVICE , OPENAI_STORAGE_USER )
766785 . context ( "failed to access keyring for openai session" ) ?;
@@ -1245,6 +1264,39 @@ mod tests {
12451264 . expect ( "clear session" ) ;
12461265 }
12471266
1267+ #[ test]
1268+ #[ serial]
1269+ fn clear_openai_chatgpt_session_removes_file_and_keyring_sessions ( ) {
1270+ let _guard = TestAuthDirGuard :: new ( ) ;
1271+ let session = sample_session ( ) ;
1272+ save_openai_chatgpt_session_with_mode ( & session, AuthCredentialsStoreMode :: File )
1273+ . expect ( "save file session" ) ;
1274+
1275+ if save_openai_chatgpt_session_with_mode ( & session, AuthCredentialsStoreMode :: Keyring )
1276+ . is_err ( )
1277+ {
1278+ clear_openai_chatgpt_session ( ) . expect ( "clear session" ) ;
1279+ assert ! (
1280+ load_openai_chatgpt_session_with_mode( AuthCredentialsStoreMode :: File )
1281+ . expect( "load file session" )
1282+ . is_none( )
1283+ ) ;
1284+ return ;
1285+ }
1286+
1287+ clear_openai_chatgpt_session ( ) . expect ( "clear session" ) ;
1288+ assert ! (
1289+ load_openai_chatgpt_session_with_mode( AuthCredentialsStoreMode :: File )
1290+ . expect( "load file session" )
1291+ . is_none( )
1292+ ) ;
1293+ assert ! (
1294+ load_openai_chatgpt_session_with_mode( AuthCredentialsStoreMode :: Keyring )
1295+ . expect( "load keyring session" )
1296+ . is_none( )
1297+ ) ;
1298+ }
1299+
12481300 #[ test]
12491301 fn active_api_bearer_token_falls_back_to_access_token ( ) {
12501302 let mut session = sample_session ( ) ;
0 commit comments