|
1 | 1 | using System.Diagnostics; |
2 | 2 | using System.IO.Abstractions; |
| 3 | +using System.Text; |
3 | 4 | using System.Text.Json; |
4 | 5 | using Microsoft.Extensions.Logging; |
5 | 6 |
|
@@ -184,14 +185,33 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon |
184 | 185 |
|
185 | 186 | using var process = new Process { StartInfo = startInfo }; |
186 | 187 |
|
| 188 | + var stdoutBuilder = new StringBuilder(); |
| 189 | + var stderrBuilder = new StringBuilder(); |
| 190 | + |
187 | 191 | process.Start(); |
188 | 192 |
|
189 | | - // Start reading stdout and stderr concurrently before writing stdin to prevent |
190 | | - // buffer deadlocks. ReadToEndAsync guarantees all output is drained before the |
191 | | - // tasks complete, unlike BeginOutputReadLine which can leave data in the buffer |
192 | | - // after WaitForExitAsync returns. |
193 | | - var stdoutTask = process.StandardOutput.ReadToEndAsync(); |
194 | | - var stderrTask = process.StandardError.ReadToEndAsync(); |
| 193 | + // Read stdout and stderr line-by-line in background tasks so output streams |
| 194 | + // to the console in real-time (visible in pipeline logs) while also being |
| 195 | + // accumulated for the return value. ReadLineAsync returns null at EOF. |
| 196 | + var stdoutTask = Task.Run(async () => |
| 197 | + { |
| 198 | + string? line; |
| 199 | + while ((line = await process.StandardOutput.ReadLineAsync()) != null) |
| 200 | + { |
| 201 | + Console.WriteLine(line); |
| 202 | + stdoutBuilder.AppendLine(line); |
| 203 | + } |
| 204 | + }); |
| 205 | + |
| 206 | + var stderrTask = Task.Run(async () => |
| 207 | + { |
| 208 | + string? line; |
| 209 | + while ((line = await process.StandardError.ReadLineAsync()) != null) |
| 210 | + { |
| 211 | + Console.Error.WriteLine(line); |
| 212 | + stderrBuilder.AppendLine(line); |
| 213 | + } |
| 214 | + }); |
195 | 215 |
|
196 | 216 | // Pipe the user prompt to stdin and close it |
197 | 217 | await process.StandardInput.WriteLineAsync(userPrompt); |
@@ -219,16 +239,11 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon |
219 | 239 | throw new TimeoutException($"Claude Code CLI did not complete within {ProcessTimeout.TotalMinutes} minutes. The process was terminated."); |
220 | 240 | } |
221 | 241 |
|
222 | | - // Await stream reads after exit to ensure all buffered output has been captured |
223 | | - var stdout = await stdoutTask; |
224 | | - var stderr = await stderrTask; |
225 | | - |
226 | | - foreach (var line in stderr.Split('\n', StringSplitOptions.RemoveEmptyEntries)) |
227 | | - { |
228 | | - logger.LogDebug("[claude stderr] {Line}", line.TrimEnd('\r')); |
229 | | - } |
| 242 | + // Ensure all buffered output has been fully read before returning |
| 243 | + await stdoutTask; |
| 244 | + await stderrTask; |
230 | 245 |
|
231 | | - return (process.ExitCode, stdout, stderr); |
| 246 | + return (process.ExitCode, stdoutBuilder.ToString(), stderrBuilder.ToString()); |
232 | 247 | } |
233 | 248 |
|
234 | 249 | /// <summary> |
|
0 commit comments