Skip to content

Stream build output live instead of swallowing it - #26

Merged
TakiTake merged 1 commit into
mainfrom
feat/stream-build-logs
Jul 11, 2026
Merged

Stream build output live instead of swallowing it#26
TakiTake merged 1 commit into
mainfrom
feat/stream-build-logs

Conversation

@TakiTake

Copy link
Copy Markdown
Owner

Summary

Closes #13. pall8t build, and the implicit rebuild inside pall8t run, used to go through util::run_ok — which captures all of container build's stdout+stderr via Command::output() and discards it on success. A multi-minute build looked completely silent, and the user had no way to tell it was making progress. No -v/--quiet flag: a silent build is simply wrong, so streaming is always on.

  • Added util::run_streaming(program, args) -> Result<()>, a sibling to run_ok for commands whose output is meant to be shown, not parsed. The child's stderr is inherited and its stdout is redirected to a dup of pall8t's own stderr fd (std::io::stderr().as_fd().try_clone_to_owned() + Stdio::from(...)) — not a forwarding thread — so both streams interleave live with pall8t's own eprintln! progress messages, while pall8t's own stdout stays untouched (pall8t build still prints built <tag> there; pall8t ls --json and the herdr integration depend on that staying clean).
  • container::build_image now calls run_streaming instead of run_ok. run_ok itself is unchanged — its callers (container list, image list, git) still need captured, parseable output.
  • Stdin is explicitly closed (Stdio::null()). This mattered more than it looked: Command::status() (used by run_streaming) inherits stdin by default, unlike Command::output() (used by run_ok), which closes it. Left alone, a piped prompt or the controlling TTY would have been handed to the build child — an adversarial review pass caught this before it shipped.
  • image::try_build's "building..." banner tweaked (trailing : instead of ) now that real build output immediately follows it.
  • README.md / docs/requirements.md note that build output streams to stderr by default, worded to describe this specific path rather than a blanket "pall8t's stdout is always clean" claim (a neighboring, pre-existing code path — container::system_start, invoked by ensure_container_system before cmd_ls's JSON print — still fully inherits stdio and isn't covered by this change; flagged below, not fixed here as it's a separate, unrelated gap).

Test plan

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test — 127 tests pass, including 3 new ones for run_streaming's error contract (nonzero exit carries the command line, missing binary is an Err); the stdout/stderr-split property itself is not unit-tested — documented in the test module why an in-process test for it would be unsafe under cargo test's parallel harness (would require swapping this test binary's own fd 1, corrupting concurrently running tests' output) rather than forcing a fragile test
  • Ran /code-review (self) and /skeptical-review (adversarial agent) — findings addressed in the commit, most notably the stdin-inheritance issue above

Note for a possible follow-up (not fixed in this PR, out of scope for #13)

container::system_start (src/container.rs) uses full stdio inheritance and is called by ensure_container_system() before cmd_ls prints its JSON — if container system start ever writes to its own stdout, that would leak into pall8t ls --json's output the same way build output used to leak. Separate code path, separate bug, worth its own issue if it turns out to matter in practice.

container build's stdout+stderr used to be fully captured by
Command::output() and discarded on success, so a multi-minute build
looked completely silent/hung. Add util::run_streaming, a sibling to
run_ok for commands whose output is shown rather than parsed: the
child's stderr is inherited and its stdout is redirected to a dup of
our own stderr (not a forwarding thread), so both stream live while
pall8t's own stdout stays clean for `built <tag>` / `ls --json`. No
flag — a silent build is simply wrong, so this is always on.

Stdin is explicitly closed (Stdio::null()): Command::status()'s
default of inheriting it, unlike output()'s default of closing it,
would otherwise hand a piped prompt or the controlling TTY to the
build child.

container::build_image now goes through run_streaming instead of
run_ok. run_ok itself is untouched; its parsing callers still need
captured output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@TakiTake
TakiTake merged commit be42b12 into main Jul 11, 2026
2 checks passed
@TakiTake
TakiTake deleted the feat/stream-build-logs branch July 11, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pall8t buildのログが見たい

1 participant