Send progress output to stderr, so stdout carries only a command's own output #48

Open
opened 2026-11-01 01:09:24 +00:00 by jorijnvdgraaf · 0 comments

Problem

crafter-build writes its progress to stdout:

  • Progress.cpp:70 decides whether there is a terminal with isatty(STDOUT_FILENO) (and line 26 takes the width from stdout).
  • Without a terminal, every finished task prints [n/m] label to stdout (Progress.cpp:99). So do the closing "Built N steps in Xms" line and the -v command echo (EchoCommand, Progress.cpp:104).
  • ScriptContext::Run echoes $ cmd to stdout (Crafter.Build-Script.cpp:24-29).

Anything that pipes crafter-build's output therefore gets progress mixed into it. For example, crafter-build run <script> --json | jq fails, because every load prints [n/m] Updating … for the unpinned dependencies it pulls (Crafter.Build-External.cpp:427-432) before the script even starts. Agents drive crafter-build through a Bash tool without a terminal, so they see the same lines in the output of every command.

Proposal

This is the version agreed with Jorijn.

  • All console progress goes to stderr:
    • the in-place status line on a terminal;
    • the [n/m] label lines without one;
    • the closing "Built N steps in Xms" line;
    • the -v command echo;
    • probably also ScriptContext::Run's $ cmd echo, which is crafter-build's own chatter (like set -x), not the script's output.
  • Terminal detection follows stderr: isatty(STDERR_FILENO), and the width from stderr too. Then crafter-build run x --json | jq still draws the status line on the terminal while jq gets clean JSON.
  • stdout keeps only the command's actual output: the program's or script's own prints, --version, --help, the --list listings, test results and lint findings.
  • run keeps pulling unpinned dependencies, like every other verb. An explicit --offline for all verbs can come later if someone needs it.
  • This is user-visible: crafter-build > build.log now needs 2>&1 to keep the progress.
    • Say so in the README in the same PR, next to the "outputs land at" paragraph in "## Writing a project.cpp" or in the CLI help text.
    • Leave "## Install" and the new "## Debugging" section alone; claude/flatpak-debug-tools edits both.

Done when

  • Without a terminal, crafter-build run <script> | cat shows only the script's output, and the progress goes to stderr.
  • A test checks that the stdout of a fixture build and run without a terminal holds no progress lines.
  • The README says that a log of the progress needs 2>&1.

Context

This came up while designing Crafter.Debug, which first planned to run its client through crafter-build run debug. Crafter.Debug now uses a launcher (build/crafter-debug) instead, so nothing waits on this; it is a general fix. The per-call load cost that ruled out the script route is #47.

## Problem crafter-build writes its progress to stdout: - `Progress.cpp:70` decides whether there is a terminal with `isatty(STDOUT_FILENO)` (and line 26 takes the width from stdout). - Without a terminal, every finished task prints `[n/m] label` to stdout (`Progress.cpp:99`). So do the closing "Built N steps in Xms" line and the `-v` command echo (`EchoCommand`, `Progress.cpp:104`). - `ScriptContext::Run` echoes `$ cmd` to stdout (`Crafter.Build-Script.cpp:24-29`). Anything that pipes crafter-build's output therefore gets progress mixed into it. For example, `crafter-build run <script> --json | jq` fails, because every load prints `[n/m] Updating …` for the unpinned dependencies it pulls (`Crafter.Build-External.cpp:427-432`) before the script even starts. Agents drive crafter-build through a Bash tool without a terminal, so they see the same lines in the output of every command. ## Proposal This is the version agreed with Jorijn. - **All console progress goes to stderr:** - the in-place status line on a terminal; - the `[n/m] label` lines without one; - the closing "Built N steps in Xms" line; - the `-v` command echo; - probably also `ScriptContext::Run`'s `$ cmd` echo, which is crafter-build's own chatter (like `set -x`), not the script's output. - **Terminal detection follows stderr:** `isatty(STDERR_FILENO)`, and the width from stderr too. Then `crafter-build run x --json | jq` still draws the status line on the terminal while jq gets clean JSON. - **stdout keeps only the command's actual output:** the program's or script's own prints, `--version`, `--help`, the `--list` listings, test results and lint findings. - **`run` keeps pulling unpinned dependencies,** like every other verb. An explicit `--offline` for all verbs can come later if someone needs it. - **This is user-visible:** `crafter-build > build.log` now needs `2>&1` to keep the progress. - Say so in the README in the same PR, next to the "outputs land at" paragraph in "## Writing a project.cpp" or in the CLI help text. - Leave "## Install" and the new "## Debugging" section alone; `claude/flatpak-debug-tools` edits both. ## Done when - Without a terminal, `crafter-build run <script> | cat` shows only the script's output, and the progress goes to stderr. - A test checks that the stdout of a fixture build and run without a terminal holds no progress lines. - The README says that a log of the progress needs `2>&1`. ## Context This came up while designing Crafter.Debug, which first planned to run its client through `crafter-build run debug`. Crafter.Debug now uses a launcher (`build/crafter-debug`) instead, so nothing waits on this; it is a general fix. The per-call load cost that ruled out the script route is #47.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Catcrafts/Crafter.Build#48
No description provided.