@@ -75,6 +75,7 @@ const useSSERequest = <T = string>(
7575 let accumulatedContent = '' ;
7676 let requestHandle : SSERequestHandle | undefined ;
7777 let callbackHandledError = false ;
78+ let terminalCallbackFired = false ;
7879 const isActiveRequest = ( ) => mountedRef . current && requestOwner . owns ( generation , requestHandle ) ;
7980
8081 try {
@@ -83,10 +84,12 @@ const useSSERequest = <T = string>(
8384 guardSSERequestCallbacks < SSEOutput > (
8485 {
8586 onSuccess : ( ) => {
87+ terminalCallbackFired = true ;
8688 console . log ( '[useSSERequest] onSuccess, chunks count:' , chunks . length ) ;
8789 setStatus ( SSERequestStatus . FINISH ) ;
8890 } ,
8991 onError : ( err ) => {
92+ terminalCallbackFired = true ;
9093 callbackHandledError = true ;
9194 console . log ( '[useSSERequest] onError:' , err ) ;
9295 setStatus ( SSERequestStatus . ERROR ) ;
@@ -117,6 +120,7 @@ const useSSERequest = <T = string>(
117120 }
118121 } ,
119122 onStop : ( ) => {
123+ terminalCallbackFired = true ;
120124 console . log ( '[useSSERequest] onStop' ) ;
121125 setStatus ( SSERequestStatus . FINISH ) ;
122126 } ,
@@ -136,6 +140,12 @@ const useSSERequest = <T = string>(
136140 setStatus ( SSERequestStatus . ERROR ) ;
137141 setError ( error_ instanceof Error ? error_ : new Error ( 'Unknown error' ) ) ;
138142 } finally {
143+ if ( ! terminalCallbackFired && mountedRef . current && requestOwner . isCurrentGeneration ( generation ) ) {
144+ // A stream can end without a terminal callback, for example when the backend answers with a
145+ // business error payload. Never leave the request in LOADING, otherwise the UI keeps showing
146+ // its generating state next to an error that already arrived.
147+ setStatus ( SSERequestStatus . ERROR ) ;
148+ }
139149 if ( requestHandle ) {
140150 requestOwner . release ( generation , requestHandle ) ;
141151 }
0 commit comments