@@ -58,6 +58,7 @@ type ClaudeLiveTurn = {
5858 timeoutTimer : NodeJS . Timeout | null ;
5959 activeToolTimer : NodeJS . Timeout | null ;
6060 activeTools : Map < string , ClaudeLiveActiveTool > ;
61+ observedStdout : boolean ;
6162 streamingParser : ReturnType < typeof createCliJsonlStreamingParser > ;
6263 execPermission : ClaudeLiveExecPermission ;
6364 resolve : ( output : CliOutput ) => void ;
@@ -73,8 +74,6 @@ type ClaudeLiveSession = {
7374 stderr : string ;
7475 stdoutBuffer : string ;
7576 currentTurn : ClaudeLiveTurn | null ;
76- drainTimer : NodeJS . Timeout | null ;
77- drainingAbortedTurn : boolean ;
7877 idleTimer : NodeJS . Timeout | null ;
7978 cleanup : ( ) => Promise < void > ;
8079 cleanupPromise : Promise < void > | null ;
@@ -375,13 +374,6 @@ function clearTurnTimers(turn: ClaudeLiveTurn): void {
375374 }
376375}
377376
378- function clearDrainTimer ( session : ClaudeLiveSession ) : void {
379- if ( session . drainTimer ) {
380- clearTimeout ( session . drainTimer ) ;
381- session . drainTimer = null ;
382- }
383- }
384-
385377function finishTurn ( session : ClaudeLiveSession , output : CliOutput ) : void {
386378 const turn = session . currentTurn ;
387379 if ( ! turn ) {
@@ -447,7 +439,6 @@ function closeLiveSession(
447439 clearTimeout ( session . idleTimer ) ;
448440 session . idleTimer = null ;
449441 }
450- clearDrainTimer ( session ) ;
451442 if ( liveSessions . get ( session . key ) === session ) {
452443 liveSessions . delete ( session . key ) ;
453444 }
@@ -854,20 +845,10 @@ function handleClaudeLiveLine(session: ClaudeLiveSession, line: string): void {
854845 return ;
855846 }
856847 const parsed = parseClaudeLiveJsonLine ( session , trimmed ) ;
857- if ( ! parsed ) {
858- return ;
848+ if ( turn ) {
849+ turn . observedStdout = true ;
859850 }
860- if ( session . drainingAbortedTurn ) {
861- if ( parsed . type === "result" ) {
862- const turnToClear = session . currentTurn ;
863- if ( turnToClear ) {
864- clearTurnTimers ( turnToClear ) ;
865- session . currentTurn = null ;
866- }
867- session . drainingAbortedTurn = false ;
868- clearDrainTimer ( session ) ;
869- scheduleIdleClose ( session ) ;
870- }
851+ if ( ! parsed ) {
871852 return ;
872853 }
873854 if ( ! turn ) {
@@ -940,7 +921,6 @@ function handleClaudeExit(session: ClaudeLiveSession, exitCode: number | null):
940921 clearTimeout ( session . idleTimer ) ;
941922 session . idleTimer = null ;
942923 }
943- clearDrainTimer ( session ) ;
944924 if ( liveSessions . get ( session . key ) === session ) {
945925 liveSessions . delete ( session . key ) ;
946926 }
@@ -965,8 +945,22 @@ function handleClaudeExit(session: ClaudeLiveSession, exitCode: number | null):
965945 const fallbackMessage =
966946 exitCode === 0 ? "Claude CLI exited before completing the turn." : "Claude CLI failed." ;
967947 const message = extractCliErrorMessage ( stderr ) ?? ( stderr || fallbackMessage ) ;
968- if ( exitCode === 0 ) {
969- failTurn ( session , new Error ( message ) ) ;
948+ if ( exitCode === 0 && ! stderr ) {
949+ const turn = session . currentTurn ;
950+ const retryCode =
951+ turn && ! turn . observedStdout && turn . rawLines . length === 0
952+ ? "cli_unknown_empty_failure"
953+ : undefined ;
954+ failTurn (
955+ session ,
956+ new FailoverError ( message , {
957+ reason : "empty_response" ,
958+ provider : session . providerId ,
959+ model : session . modelId ,
960+ status : resolveFailoverStatus ( "empty_response" ) ,
961+ code : retryCode ,
962+ } ) ,
963+ ) ;
970964 return ;
971965 }
972966 const reason = classifyFailoverReason ( message , { provider : session . providerId } ) ?? "unknown" ;
@@ -1076,8 +1070,6 @@ async function createClaudeLiveSession(params: {
10761070 stderr : "" ,
10771071 stdoutBuffer : "" ,
10781072 currentTurn : null ,
1079- drainTimer : null ,
1080- drainingAbortedTurn : false ,
10811073 idleTimer : null ,
10821074 cleanup : async ( ) => {
10831075 await mcpCaptureAttempt . cleanup ?.( ) ;
@@ -1129,6 +1121,7 @@ function createTurn(params: {
11291121 timeoutTimer : null ,
11301122 activeToolTimer : null ,
11311123 activeTools : new Map ( ) ,
1124+ observedStdout : false ,
11321125 streamingParser : createCliJsonlStreamingParser ( {
11331126 backend : params . context . preparedBackend . backend ,
11341127 providerId : params . context . backendResolved . id ,
@@ -1167,7 +1160,7 @@ function createTurn(params: {
11671160
11681161function closeOldestIdleSession ( ) : boolean {
11691162 for ( const session of liveSessions . values ( ) ) {
1170- if ( ! session . currentTurn && ! session . drainingAbortedTurn ) {
1163+ if ( ! session . currentTurn ) {
11711164 closeLiveSession ( session , "idle" ) ;
11721165 return true ;
11731166 }
@@ -1312,7 +1305,7 @@ export async function runClaudeLiveSessionTurn(params: {
13121305 await cleanup ( ) ;
13131306 throw new Error ( "Claude CLI live session closed before handling the turn" ) ;
13141307 }
1315- if ( session . currentTurn || session . drainingAbortedTurn ) {
1308+ if ( session . currentTurn ) {
13161309 throw new Error ( "Claude CLI live session is already handling a turn" ) ;
13171310 }
13181311 const liveSession = session ;
0 commit comments