Skip to content

Commit fe29004

Browse files
committed
Improve real-time logging for Claude Code CLI process by capturing stdout and stderr line-by-line, enhancing visibility in pipeline logs.
1 parent 9a94213 commit fe29004

1 file changed

Lines changed: 30 additions & 15 deletions

File tree

‎src/ADOPullRequestAgent/PullRequestAgent.cs‎

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics;
22
using System.IO.Abstractions;
3+
using System.Text;
34
using System.Text.Json;
45
using Microsoft.Extensions.Logging;
56

@@ -184,14 +185,33 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon
184185

185186
using var process = new Process { StartInfo = startInfo };
186187

188+
var stdoutBuilder = new StringBuilder();
189+
var stderrBuilder = new StringBuilder();
190+
187191
process.Start();
188192

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+
});
195215

196216
// Pipe the user prompt to stdin and close it
197217
await process.StandardInput.WriteLineAsync(userPrompt);
@@ -219,16 +239,11 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon
219239
throw new TimeoutException($"Claude Code CLI did not complete within {ProcessTimeout.TotalMinutes} minutes. The process was terminated.");
220240
}
221241

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;
230245

231-
return (process.ExitCode, stdout, stderr);
246+
return (process.ExitCode, stdoutBuilder.ToString(), stderrBuilder.ToString());
232247
}
233248

234249
/// <summary>

0 commit comments

Comments
 (0)