Skip to content

fix: apply -q/non-TTY auto-quiet to a piped-stdin message (gh #93) - #94

Merged
dkedar7 merged 1 commit into
mainfrom
fix/93-quiet-stdin-message
Jul 23, 2026
Merged

dkedar7 merged 1 commit into
mainfrom
fix/93-quiet-stdin-message

Conversation

@dkedar7

@dkedar7 dkedar7 commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Issue

Closes #93.

-q/--quiet (and the non-TTY auto-quiet from #53) is documented to suppress the header, spinner, tool chatter, timing, and color and emit only the agent's reply. That contract held for a single-shot run (a MESSAGE arg or -f/--file) but not for the most idiomatic pipe form, echo "hi" | langstage-cli --demo -q: a stdin message is consumed by the interactive loop, which honored neither -q nor the stdout.isatty()==False auto-quiet — so it still emitted the ···· separator rules, the prompt row (with \x01\x02 bracketed-paste bytes), the Nms timing line, and a trailing Goodbye!. A script doing answer=$(echo "$q" | langstage-cli -q …) captured ~380 bytes of chrome around a 25-byte reply.

Root cause (two, both fixed)

  1. Auto-quiet never fired for stdin. _QUIET keyed off (message or prompt_file or verify_agent) — a stdin-fed message is none of those, so the non-TTY auto-quiet was skipped.
  2. The interactive loop applied no quiet gating at all — it always printed the separators, prompt, timing line and Goodbye!, regardless of _QUIET.

Fix

  • The auto-quiet gate is now "stdout is not a TTY and the input is non-interactive" — a single-shot (MESSAGE/-f/--verify) or piped (non-TTY) stdin.
  • The conversation loop honors _QUIET: it suppresses its ···· separators, the prompt (input() with no prompt string, so no glyph and no bracketed-paste bytes), the Nms timing line, and the Goodbye!, capping each reply with a single newline — byte-identical to the -q "msg" arg path.

Why quiet-the-loop, not reroute-to-single-shot

Piped stdin genuinely drives the loop in this CLI: it reads one line per turn via input() until EOF, so it can carry multiple messages and slash commands (printf 'hi\nbye\n' | … runs two turns; echo /config | … drives the command — the latter is exercised by an existing test). Rerouting a whole-stdin read to single-shot would collapse that capability. Quieting the loop keeps it while making every turn clean.

Before / after — echo "hi" | langstage-cli --demo -q

bytes (stdout) content
before ~380 ···· rules, prompt, Nms timing, reply, Goodbye!
after 25 (demo agent) You said: hi — identical to the -q "hi" arg path

Tests

Both fail on 0.6.22 and pass here. Full suite: 142 passed (140 baseline + 2). ruff check . and ruff format --check . both clean. A live TTY session (stdin is a tty, no -q) keeps _QUIET == False, so its header, separators, prompt, timing and farewell render unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_011HWCfJii6gXd3XL3Gq3W8B

`-q/--quiet` and the non-TTY auto-quiet (#53) suppress the header, spinner,
tool chatter, timing, color and Goodbye so a scriptable run emits only the
reply. That held for a single-shot (a MESSAGE arg or -f/--file) but not for the
most idiomatic pipe, `echo "hi" | langstage-cli --demo -q`: a stdin message is
consumed by the interactive loop, which honored neither -q nor the
`stdout.isatty()==False` gating — so it still leaked the `····` separators, the
`❯` prompt (with `\x01\x02` bracketed-paste bytes), the `Nms` timing line and a
trailing `Goodbye!` (~380 bytes of chrome around a 25-byte reply).

Two causes, both fixed: (1) the auto-quiet formula keyed only off a
MESSAGE/-f/--verify single-shot, so a stdin-fed run never triggered it — the
gate is now "stdout not a TTY AND non-interactive input", i.e. a single-shot OR
piped (non-TTY) stdin; and (2) the conversation loop applied no quiet gating —
it now suppresses its separators, prompt, timing line and `Goodbye!` under
_QUIET, capping each reply with a single newline exactly like the -q arg path.

The loop is kept (not rerouted to single-shot) on purpose: piped stdin can
carry multiple lines and slash commands (`printf 'hi\nbye\n' | …` runs two
clean turns; `echo /config | …` still drives the command), each turn now just
rendered on the scriptable path. A live terminal session (stdin IS a tty, no
-q) is unchanged — header, separators, prompt, timing and farewell all stay.

Regression tests: piped-stdin quiet output is byte-identical to the -q arg path
(25 bytes, no timing/`····`/`❯`/Goodbye), and piped stdin auto-quiets with no
flag. Both fail on 0.6.22.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HWCfJii6gXd3XL3Gq3W8B
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.

-q/--quiet (and non-TTY auto-quiet) is ignored when the message comes from piped stdin — chrome + Nms timing leak into stdout

1 participant