@@ -232,14 +232,14 @@ async Task<Connection> StartCoreAsync(CancellationToken ct)
232232 {
233233 // External server (TCP)
234234 _actualPort = _optionsPort ;
235- result = ConnectToServerAsync ( null , _optionsHost , _optionsPort , null , ct ) ;
235+ result = ConnectToServerAsync ( null , _optionsHost , _optionsPort , null , null , ct ) ;
236236 }
237237 else
238238 {
239239 // Child process (stdio or TCP)
240- var ( cliProcess , portOrNull , stderrBuffer ) = await StartCliServerAsync ( _options , _effectiveConnectionToken , _logger , ct ) ;
240+ var ( cliProcess , portOrNull , stderrBuffer , stderrReader ) = await StartCliServerAsync ( _options , _effectiveConnectionToken , _logger , ct ) ;
241241 _actualPort = portOrNull ;
242- result = ConnectToServerAsync ( cliProcess , portOrNull is null ? null : "localhost" , portOrNull , stderrBuffer , ct ) ;
242+ result = ConnectToServerAsync ( cliProcess , portOrNull is null ? null : "localhost" , portOrNull , stderrBuffer , stderrReader , ct ) ;
243243 }
244244
245245 var connection = await result ;
@@ -1076,21 +1076,19 @@ internal static async Task InvokeRpcAsync(JsonRpc rpc, string method, object?[]?
10761076 }
10771077
10781078 internal static async Task < T > InvokeRpcAsync < T > ( JsonRpc rpc , string method , object ? [ ] ? args , StringBuilder ? stderrBuffer , CancellationToken cancellationToken )
1079+ {
1080+ return await InvokeRpcAsync < T > ( rpc , method , args , stderrBuffer , stderrReader : null , cancellationToken ) ;
1081+ }
1082+
1083+ internal static async Task < T > InvokeRpcAsync < T > ( JsonRpc rpc , string method , object ? [ ] ? args , StringBuilder ? stderrBuffer , Task ? stderrReader , CancellationToken cancellationToken )
10791084 {
10801085 try
10811086 {
10821087 return await rpc . InvokeAsync < T > ( method , args , cancellationToken ) ;
10831088 }
10841089 catch ( ConnectionLostException ex )
10851090 {
1086- string ? stderrOutput = null ;
1087- if ( stderrBuffer is not null )
1088- {
1089- lock ( stderrBuffer )
1090- {
1091- stderrOutput = stderrBuffer . ToString ( ) . Trim ( ) ;
1092- }
1093- }
1091+ var stderrOutput = await GetStderrOutputAsync ( stderrBuffer , stderrReader ) ;
10941092
10951093 if ( ! string . IsNullOrEmpty ( stderrOutput ) )
10961094 {
@@ -1111,13 +1109,34 @@ private static string FormatCliExitedMessage(string message, string stderrOutput
11111109 : $ "{ message } \n stderr: { stderrOutput } ";
11121110 }
11131111
1114- private static IOException CreateCliExitedException ( string message , StringBuilder stderrBuffer )
1112+ private static async Task < string ? > GetStderrOutputAsync ( StringBuilder ? stderrBuffer , Task ? stderrReader )
1113+ {
1114+ if ( stderrBuffer is null )
1115+ {
1116+ return null ;
1117+ }
1118+
1119+ var stderrOutput = ReadStderrBuffer ( stderrBuffer ) ;
1120+ if ( string . IsNullOrEmpty ( stderrOutput ) && stderrReader is not null )
1121+ {
1122+ await Task . WhenAny ( stderrReader , Task . Delay ( TimeSpan . FromMilliseconds ( 250 ) ) ) ;
1123+ stderrOutput = ReadStderrBuffer ( stderrBuffer ) ;
1124+ }
1125+
1126+ return string . IsNullOrEmpty ( stderrOutput ) ? null : stderrOutput ;
1127+ }
1128+
1129+ private static string ReadStderrBuffer ( StringBuilder stderrBuffer )
11151130 {
1116- string stderrOutput ;
11171131 lock ( stderrBuffer )
11181132 {
1119- stderrOutput = stderrBuffer . ToString ( ) . Trim ( ) ;
1133+ return stderrBuffer . ToString ( ) . Trim ( ) ;
11201134 }
1135+ }
1136+
1137+ private static IOException CreateCliExitedException ( string message , StringBuilder stderrBuffer )
1138+ {
1139+ var stderrOutput = ReadStderrBuffer ( stderrBuffer ) ;
11211140
11221141 return new IOException ( FormatCliExitedMessage ( message , stderrOutput ) ) ;
11231142 }
@@ -1171,15 +1190,15 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
11711190 try
11721191 {
11731192 var connectResponse = await InvokeRpcAsync < ConnectResult > (
1174- connection . Rpc , "connect" , [ new ConnectRequest { Token = _effectiveConnectionToken } ] , connection . StderrBuffer , cancellationToken ) ;
1193+ connection . Rpc , "connect" , [ new ConnectRequest { Token = _effectiveConnectionToken } ] , connection . StderrBuffer , connection . StderrReader , cancellationToken ) ;
11751194 serverVersion = ( int ) connectResponse . ProtocolVersion ;
11761195 }
11771196 catch ( IOException ex ) when ( ex . InnerException is RemoteRpcException remoteEx && IsUnsupportedConnectMethod ( remoteEx ) )
11781197 {
11791198 // Legacy server without `connect`; fall back to `ping`. A token, if any,
11801199 // is silently dropped — the legacy server can't enforce one.
11811200 var pingResponse = await InvokeRpcAsync < PingResponse > (
1182- connection . Rpc , "ping" , [ new PingRequest ( ) ] , connection . StderrBuffer , cancellationToken ) ;
1201+ connection . Rpc , "ping" , [ new PingRequest ( ) ] , connection . StderrBuffer , connection . StderrReader , cancellationToken ) ;
11831202 serverVersion = pingResponse . ProtocolVersion ;
11841203 }
11851204
@@ -1208,7 +1227,7 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex)
12081227 || string . Equals ( ex . Message , "Unhandled method connect" , StringComparison . Ordinal ) ;
12091228 }
12101229
1211- private static async Task < ( Process Process , int ? DetectedLocalhostTcpPort , StringBuilder StderrBuffer ) > StartCliServerAsync ( CopilotClientOptions options , string ? connectionToken , ILogger logger , CancellationToken cancellationToken )
1230+ private static async Task < ( Process Process , int ? DetectedLocalhostTcpPort , StringBuilder StderrBuffer , Task StderrReader ) > StartCliServerAsync ( CopilotClientOptions options , string ? connectionToken , ILogger logger , CancellationToken cancellationToken )
12121231 {
12131232 // Use explicit path, COPILOT_CLI_PATH env var (from options.Environment or process env), or bundled CLI - no PATH fallback
12141233 var envCliPath = options . Environment is not null && options . Environment . TryGetValue ( "COPILOT_CLI_PATH" , out var envValue ) ? envValue
@@ -1356,7 +1375,7 @@ private static bool IsUnsupportedConnectMethod(RemoteRpcException ex)
13561375 }
13571376 }
13581377
1359- return ( cliProcess , detectedLocalhostTcpPort , stderrBuffer ) ;
1378+ return ( cliProcess , detectedLocalhostTcpPort , stderrBuffer , stderrReader ) ;
13601379 }
13611380
13621381 private static string ? GetBundledCliPath ( out string searchedPath )
@@ -1400,7 +1419,7 @@ private static (string FileName, IEnumerable<string> Args) ResolveCliCommand(str
14001419 return ( cliPath , args ) ;
14011420 }
14021421
1403- private async Task < Connection > ConnectToServerAsync ( Process ? cliProcess , string ? tcpHost , int ? tcpPort , StringBuilder ? stderrBuffer , CancellationToken cancellationToken )
1422+ private async Task < Connection > ConnectToServerAsync ( Process ? cliProcess , string ? tcpHost , int ? tcpPort , StringBuilder ? stderrBuffer , Task ? stderrReader , CancellationToken cancellationToken )
14041423 {
14051424 Stream inputStream , outputStream ;
14061425 NetworkStream ? networkStream = null ;
@@ -1466,7 +1485,7 @@ private async Task<Connection> ConnectToServerAsync(Process? cliProcess, string?
14661485
14671486 _serverRpc = new ServerRpc ( rpc ) ;
14681487
1469- return new Connection ( rpc , cliProcess , networkStream , stderrBuffer ) ;
1488+ return new Connection ( rpc , cliProcess , networkStream , stderrBuffer , stderrReader ) ;
14701489 }
14711490
14721491 private static JsonSerializerOptions SerializerOptionsForMessageFormatter { get ; } = CreateSerializerOptions ( ) ;
@@ -1681,12 +1700,14 @@ private class Connection(
16811700 JsonRpc rpc ,
16821701 Process ? cliProcess , // Set if we created the child process
16831702 NetworkStream ? networkStream , // Set if using TCP
1684- StringBuilder ? stderrBuffer = null ) // Captures stderr for error messages
1703+ StringBuilder ? stderrBuffer = null , // Captures stderr for error messages
1704+ Task ? stderrReader = null )
16851705 {
16861706 public Process ? CliProcess => cliProcess ;
16871707 public JsonRpc Rpc => rpc ;
16881708 public NetworkStream ? NetworkStream => networkStream ;
16891709 public StringBuilder ? StderrBuffer => stderrBuffer ;
1710+ public Task ? StderrReader => stderrReader ;
16901711 }
16911712
16921713 private static class ProcessArgumentEscaper
0 commit comments