rtk-ai/rtk

bug(dotnet): build/test summary line at top of output is cut off by `| tail -N`

Closed

#1574 opened on Apr 28, 2026

View on GitHub
 (2 comments) (0 reactions) (0 assignees)Rust (48,085 stars) (2,914 forks)batch import
bugeffort-smallfilter-qualitygood first issue

Description

TL;DR

rtk dotnet build (also test, restore) puts the success/failure status line at the top of the output. Native dotnet puts it at the bottom. Anything that reads the end of the stream loses the verdict: | tail -N, watch/monitor mode in agents, IDE log tails, chunked-output readers.

Repro

rtk dotnet build 2>&1 | tail -5

Expected: the last 5 lines include ok dotnet build: 0 errors, 0 warnings (…). Actual: the last 5 lines are warnings or empty; the status line is gone.

Why agents in particular get hurt

Agents (Claude Code, Codex, Copilot CLI, etc.) commonly read build output through:

  • | tail -N to keep token usage low
  • streaming / watch consumers (/watch, /monitor, tail -f, log followers)
  • a bounded recent-output context window

In all three cases the agent only sees the last N lines or the tail of the stream. Native dotnet writes Build succeeded. or Build FAILED. last, so the agent gets a clear verdict. RTK writes the verdict first, then errors and warnings, so the agent sees error noise followed by EOF, with no ok or fail line anywhere near the end.

Result: the agent cannot tell whether the build passed, even though the information was technically there 200 lines ago. This silently breaks any agent loop that gates the next step on build success.

Why this happens (code)

src/cmds/dotnet/dotnet_cmd.rs::format_build_output (and the test/restore equivalents) emit the status line first, then errors, then warnings.

Two things compound the problem:

  1. The hook rewrites the left side of pipes. dotnet build 2>&1 | tail -5 becomes rtk dotnet build … | tail -5, so tail operates on the filtered stream, not raw dotnet output. Tracked in #1560.
  2. On failure, raw stdout/stderr is prepended before the filtered summary (dotnet_cmd.rs lines 216–228), pushing the verdict even further from the tail. PR #1115 (issue #914) drops the prepend on failure but does not move the summary line.

Suggested fixes

Two options, not mutually exclusive:

  1. Hook side (root fix, #1560): stop rewriting the left side when piping into tail, head, wc, grep, or any explicit "read-the-end" consumer. A pipe into | tail -N is an explicit user signal for raw, unreordered output.
  2. Filter side (defensive): move the status line to the bottom of format_{build,test,restore}_output, matching native dotnet. Cheap, unblocks streaming consumers and tail/head pipes today, even before #1560 lands.

Option 2 alone fixes the agent watch/monitor case described above. Option 1 alone fixes the explicit-pipe case but not streaming consumers that read RTK's stdout directly.

Related

  • #1560: hook rewrites left side of piped commands (root cause for the pipe case)
  • #914 / PR #1115: sibling dotnet output-shape issue (failure path)
  • #1545: streaming / watch-mode commands buffer or suppress stdout outside proxy mode (sibling for the streaming case)

Environment

  • Code reference: src/cmds/dotnet/dotnet_cmd.rs:986 (format_build_output builds the header first)
  • Reproduces on master (commit 4113696)

Contributor guide