fix(cli, printer): Close the chrome channel under --quiet - #1088
Open
JeanMertz wants to merge 9 commits into
Open
fix(cli, printer): Close the chrome channel under --quiet#1088JeanMertz wants to merge 9 commits into
JeanMertz wants to merge 9 commits into
Conversation
JeanMertz
force-pushed
the
json-no-escapes
branch
from
September 4, 2026 12:58
c3d1669 to
2073d38
Compare
The flag is documented as suppressing all output including errors, but the only thing it does is drop the stderr tracing layer. Every printer emission — the assistant response, tool headers, retry notices — ignores it, and the run's error message is written with `eprintln!` regardless. The ticket records where the fix belongs (the printer, the one place that sees every emission) and what has to be decided first: whether `-q` covers chrome only, everything, or graduated levels. It also collects the three documents that disagree about what the flag means. Signed-off-by: Jean Mertz <git@jeanmertz.com>
Waiting on a conversation another run holds ends in a prompt offering to keep waiting, start a new conversation, fork, or cancel. That prompt was rendered on the error stream, which is where the run's chrome goes rather than where it talks to the user. Nothing changes for a normal run, where both land on the same terminal. It matters for a run that redirects stderr, and for one that silences its chrome: a prompt that never appears while the run blocks waiting for an answer to it is a hang with no explanation. Prompts have their own channel, `/dev/tty` where one is available, which survives both. Signed-off-by: Jean Mertz <git@jeanmertz.com>
`-q` promised to suppress all output and delivered none of it: the only
thing consulting the flag was the tracing setup, so a quiet run still
printed the assistant's response, tool call headers, retry notices, and
status lines.
`-q` now silences chrome — the run's commentary on itself, everything on
stderr. Command output and the assistant's response keep stdout, because
that is the data the caller asked for, and a failing run still reports
its error. `>/dev/null` and `2>/dev/null` remain the way to drop those,
which is why the flag does not try to: the shell already expresses
"silence everything", and cannot express "chrome but not errors".
jp -q query "summarize this" > answer.md # no headers, no spinner
The policy lives on the `Printer`, the one place every emission passes,
rather than at each of the call sites that emit. `Chrome::Silenced`
discards `eprint`, `eprintln`, `erase_line`, and anything written
through `err_writer`, and leaves stdout and the prompt channel alone.
Chrome that repaints a line in place asks `chrome_repaints`, which
answers for a silenced channel and a JSON consumer at once, so the
waiting timer does not spawn a task to format a line nobody receives.
Scripts reading JP's stderr under `-q` saw output before and see none
now. `jp c grep -q` is unchanged: reporting a match through the exit
status alone is grep's own convention, and it keeps it.
Signed-off-by: Jean Mertz <git@jeanmertz.com>
The flag table said `-q` does not affect tracing, which left open whether the log file and the `JP_DEBUG` path report survive it. They do: both are developer channels, asked for by name, and a user asking for a quiet terminal is not asking to lose the record of the run. Signed-off-by: Jean Mertz <git@jeanmertz.com>
Signed-off-by: Jean Mertz <git@jeanmertz.com>
JeanMertz
force-pushed
the
fix/quiet-closes-the-chrome-channel
branch
from
September 5, 2026 16:45
e4a3297 to
0a06573
Compare
`jp -q plugin update` still announced "Refreshing plugin registry..." and its result, because those lines went to `std::io::stderr()` directly rather than through the printer that `--quiet` closes. The install path and the auto-install path that runs when an unknown subcommand matches a registry plugin printed their progress the same way. All of it is chrome and now goes through the printer, so a quiet run is quiet and a `--format json` run gets NDJSON records instead of raw text among them. The approval questions those paths ask are not chrome and keep their own channel: `Install and run it?`, `Run it?`, and the third-party confirmation move to the prompt writer, which reaches the terminal even when stderr is redirected or silenced. A question the run blocks on has to be visible. `resolve_plugin_binary` and the two helpers behind it take the printer as an argument, since they run before any renderer exists. Deciding which plugins are outdated is now separate from reporting it, which is what the new tests exercise: the network is not reachable from a unit test, but the reporting is. Signed-off-by: Jean Mertz <git@jeanmertz.com>
`jp query "..." > answer.txt` captured the role header announcing who speaks — `── jp (anthropic/claude-opus-5) ──` — along with the answer, and so did the echo of the user's own request. [RFD 048] puts the assistant's response on stdout and everything the run says about itself on stderr; the framing was on the wrong side of that line, and no amount of `--quiet` could move it, because `--quiet` closes the chrome channel and the framing was not on it. A redirected or piped run gets the answer alone now. In a terminal nothing changes: both streams are serialized through the same printer worker, so the header still lands immediately before the content it introduces. `jp conversation print` is unaffected and keeps its headers on stdout. There the same line is not narration but document structure, delimiting one stored turn from the next, and a transcript redirected to a file needs it. Both flows share `TurnView`, which is what let one classify the header for both; it now carries a `RenderFlow` naming which of the two it renders for, and the framing follows from that. [RFD 048]: https://jp.computer/rfd/048 Signed-off-by: Jean Mertz <git@jeanmertz.com>
`JP_DEBUG=1 jp query "..." > answer.txt` said nothing about the trace log it had just written. The report was gated on stdout being a terminal, so redirecting or piping the answer withdrew it — from the one person who had asked for it by name. `JP_DEBUG` is the whole question now. The report is developer output requested explicitly, and it goes to stderr, so where the run's own streams point has no bearing on it. A failed run still reports unconditionally. The gate was there to protect a program that owns the screen from two uninvited lines: in `jp … | fzf`, stderr is still the terminal, and the report lands in fzf's layout. That case is now the caller's to avoid, by unsetting the variable or redirecting stderr, which is the trade for having the report show up at all in the far more common one. Signed-off-by: Jean Mertz <git@jeanmertz.com>
`jp query "..." > answer.txt` came back with two blank lines wherever the assistant had called a tool mid-answer, one of them empty of any reason to exist. A tool call is framed by two gaps: the content above it ends with its own trailing blank line, and a second blank line follows the chrome to separate it from the content below. Both were on stdout while the chrome between them was on stderr, so a reader capturing the answer alone got the two gaps and nothing in between. The second gap goes out on the chrome channel, next to the chrome it spaces from. Stdout keeps one blank line between the two blocks, which is what two content blocks want anyway, and a terminal renders exactly as before: both streams are serialized through the same printer worker, so the order across them holds. The gap deferred when a reasoning region continues across the tool call is unchanged and stays on stdout. It ends up in front of reasoning rather than after chrome, with reasoning above it, so both sides of it are content. Signed-off-by: Jean Mertz <git@jeanmertz.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
-qpromised to suppress all output and delivered none of it: the onlything consulting the flag was the tracing setup, so a quiet run still
printed the assistant's response, tool call headers, retry notices, and
status lines.
-qnow silences chrome — the run's commentary on itself, everything onstderr. Command output and the assistant's response keep stdout, because
that is the data the caller asked for, and a failing run still reports
its error.
>/dev/nulland2>/dev/nullremain the way to drop those,which is why the flag does not try to: the shell already expresses
"silence everything", and cannot express "chrome but not errors".
The policy lives on the
Printer, the one place every emission passes,rather than at each of the call sites that emit.
Chrome::Silenceddiscards
eprint,eprintln,erase_line, and anything writtenthrough
err_writer, and leaves stdout and the prompt channel alone.Chrome that repaints a line in place asks
chrome_repaints, whichanswers for a silenced channel and a JSON consumer at once, so the
waiting timer does not spawn a task to format a line nobody receives.
Scripts reading JP's stderr under
-qsaw output before and see nonenow.
jp c grep -qis unchanged: reporting a match through the exitstatus alone is grep's own convention, and it keeps it.