Streaming/watch-mode commands still buffer or suppress stdout outside proxy mode
#1545 aperta il 26 apr 2026
Metriche repository
- Star
- (48.085 star)
- Metriche merge PR
- (Merge medio 11g 1h) (45 PR mergiate in 30 g)
Descrizione
Summary
rtk proxy streaming was fixed by the src/core/stream.rs work, but the fix is not global. Several filtered or nominally passthrough RTK command routes still do not emit stdout for long-running/watch/dev commands until the child exits, or until the parent is killed.
This is a different failure mode than the already-closed proxy-only issue: rtk proxy <cmd> streams, while rtk <recognized-filter-route> <watch/dev command> can still hang silently.
Verified behavior on current master
I tested current master (rtk 0.37.2 source tree) with fake long-running executables placed first in PATH. Each fake tool prints one line immediately and then sleeps forever. I measured whether RTK emitted the first stdout line within 2 seconds.
Streams correctly
STREAMS rtk proxy npm run dev
STREAMS rtk pnpm run dev
STREAMS rtk pnpm dev
STREAMS rtk docker logs -f web
STREAMS rtk docker compose logs -f web
STREAMS rtk kubectl logs -f web
Still broken before this fix
NO_STDOUT_WITHIN_2S rtk npm run dev
NO_STDOUT_WITHIN_2S rtk npm start
NO_STDOUT_WITHIN_2S rtk prettier --watch .
NO_STDOUT_WITHIN_2S rtk playwright codegen
NO_STDOUT_WITHIN_2S rtk gh pr checks 123 --watch
NO_STDOUT_WITHIN_2S rtk dotnet watch run
NO_STDOUT_WITHIN_2S rtk go run .
The user-visible effect is that an agent or human sees no progress and may kill/retry the command even though the child process is alive and already wrote output.
Minimal real-world repro
mkdir /tmp/rtk-stream-repro
cd /tmp/rtk-stream-repro
cat > package.json <<'JSON'
{
"scripts": {
"dev": "node -e \"console.log('dev-start'); setInterval(() => {}, 1000);\""
}
}
JSON
npm run dev # prints immediately
rtk proxy npm run dev # prints immediately
rtk npm run dev # no stdout before forced termination
Root cause
The merged streaming infrastructure is present, but not all command paths use it.
Important paths:
src/core/runner.rs:RunMode::PassthroughusesFilterMode::Passthrough, which inherits stdout/stderr and streams.src/core/runner.rs:RunMode::Filteredcallsstream::run_streaming(..., FilterMode::CaptureOnly), which captures stdout/stderr until EOF before applying a filter.src/core/stream.rs:exec_capture()still usescmd.output(), which waits for the child process to exit.- Some functions named
run_passthroughwere not actually streaming passthrough. For example, unsupportedgoanddotnetroutes captured output and printed it only after completion.
So #222 was fixed for rtk proxy, but filtered routes and some passthrough-named routes remained EOF-buffered.
Related issues and PRs
- #222: fixed the narrow
rtk proxylong-running streaming bug. This issue is not the same becausertk proxy npm run devstreams whilertk npm run devdoes not. - #361: broad hook/streaming issue. It correctly identified
.output()buffering, but the merged stream infrastructure did not eliminate all buffered filter paths. - #956: merged
src/core/stream.rs,FilterMode, andrtk proxystreaming. This introduced the needed primitives, but many routes still useCaptureOnlyorexec_capture(). - #1267: related Docker logs/progress problem. Container follow paths now tend to stream via passthrough, but filtered log summarization can still hide progress.
- #963 and PR #1531:
git pushtimeout has the same root cause pattern (Command::output()hiding progress/prompts), but PR #1531 only fixesgit push. - #1156 and PR #1171:
--raw/--no-filteris related, but users should not need to know to opt out manually for commands that are inherently streaming.
Suggested fix
RTK should detect inherently streaming invocations before choosing a buffered filtering path and run them with inherited stdout/stderr instead.
Practical detection heuristics:
--watch,--watchAll,-w--follow,-ffor log/follow tools such as Docker, kubectl, tail, journalctl- streaming/dev subcommands such as
watch,dev,serve,start - known interactive/long-running commands such as
playwright codegen,playwright show-report,go run,dotnet watch - inherently streaming binaries such as
tail,nodemon,watchman
Also, routes called run_passthrough should use actual passthrough execution (Stdio::inherit() / FilterMode::Passthrough), not Command::output().
Acceptance criteria
rtk npm run devemits output immediately and does not wait for EOF.rtk npm startemits output immediately and does not wait for EOF.rtk prettier --watch .emits output immediately.rtk playwright codegenemits output immediately.rtk gh pr checks <pr> --watchemits output immediately.rtk dotnet watch runemits output immediately.rtk go run .emits output immediately.- Finite commands such as
rtk npm run buildandrtk gh pr checks <pr>remain filterable.