What's wrong
LineOutputHandler.ProcessDataByLine (RunCommand/LineOutputHandler.cs, ~lines 46-57) adds each chunk to outputBuffer/errorBuffer and only calls the callback for text that ends in a line break. Nothing flushes those buffers when the process ends. AsyncProcessStreamReader returns and RunAsync completes, but the handler is never told the stream has ended.
This causes two problems:
- Last line lost. If a command's final line has no line terminator, that line never reaches the callback.
- Leak across runs. The buffers are instance fields. If the same
LineOutputHandler is reused for another command, the text left over from the first run is glued onto the start of the next run's first line.
Repro
var lines = new List<string>();
var handler = new LineOutputHandler(l => lines.Add(l));
await RunCommand.ExecuteAsync("printf", ["a\\nlast"], handler);
// lines == ["a"] — "last" is never delivered
await RunCommand.ExecuteAsync("printf", ["next\\n"], handler);
// lines == ["a", "lastnext"]
I confirmed this on Linux/net10.0 against the current main. PR #87 changes how the streams are read but does not touch this, so it still happens with #87 applied.
Why it matters
Many tools print their last line, often the result, without a newline. Examples are printf-based scripts, git rev-parse --abbrev-ref HEAD when it is wrapped by some shells, prompts and progress lines, and many Windows tools. A caller that uses LineOutputHandler to collect a result gets nothing and sees no error. The existing test helper WriteTempFileInDirectoryWithSpaces in RunCommandTests.cs already adds a newline on purpose to get around this ("would sit unflushed in its buffer"). The README describes partial lines as held "until the rest of the line arrives", but once the process has ended, the rest never arrives.
Suggested fix
- Add an internal completion hook to
OutputHandler, for example internal virtual void Complete(), that does nothing by default.
- Have
RunAsync call it after the stream reader has finished draining, but not on the cancellation or abandon path.
- In
LineOutputHandler, override it to send any non-empty outputBuffer/errorBuffer to its callback, then clear both buffers.
Acceptance criteria
printf 'a\nlast' through LineOutputHandler delivers ["a", "last"].
- Reusing one handler for two runs does not carry text from the first into the second.
- An empty trailing buffer, where output ends exactly on a newline, does not emit an extra empty line.
What's wrong
LineOutputHandler.ProcessDataByLine(RunCommand/LineOutputHandler.cs, ~lines 46-57) adds each chunk tooutputBuffer/errorBufferand only calls the callback for text that ends in a line break. Nothing flushes those buffers when the process ends.AsyncProcessStreamReaderreturns andRunAsynccompletes, but the handler is never told the stream has ended.This causes two problems:
LineOutputHandleris reused for another command, the text left over from the first run is glued onto the start of the next run's first line.Repro
I confirmed this on Linux/net10.0 against the current
main. PR #87 changes how the streams are read but does not touch this, so it still happens with #87 applied.Why it matters
Many tools print their last line, often the result, without a newline. Examples are
printf-based scripts,git rev-parse --abbrev-ref HEADwhen it is wrapped by some shells, prompts and progress lines, and many Windows tools. A caller that usesLineOutputHandlerto collect a result gets nothing and sees no error. The existing test helperWriteTempFileInDirectoryWithSpacesinRunCommandTests.csalready adds a newline on purpose to get around this ("would sit unflushed in its buffer"). The README describes partial lines as held "until the rest of the line arrives", but once the process has ended, the rest never arrives.Suggested fix
OutputHandler, for exampleinternal virtual void Complete(), that does nothing by default.RunAsynccall it after the stream reader has finished draining, but not on the cancellation or abandon path.LineOutputHandler, override it to send any non-emptyoutputBuffer/errorBufferto its callback, then clear both buffers.Acceptance criteria
printf 'a\nlast'throughLineOutputHandlerdelivers["a", "last"].