Skip to content

feat(hints): route hints + brand header to stderr; drop TTY suppression#109

Merged
TrevorBasinger merged 1 commit into
mainfrom
cg/hints-on-stderr
May 19, 2026
Merged

feat(hints): route hints + brand header to stderr; drop TTY suppression#109
TrevorBasinger merged 1 commit into
mainfrom
cg/hints-on-stderr

Conversation

@christophergeyer
Copy link
Copy Markdown
Member

Summary

The advisory `hint:` lines and the `🦖 roar ` brand header used to self-suppress in non-TTY contexts (CI, redirected output, agent capture). Stdout stayed clean for pipelines, but agents like Claude Code that capture roar output programmatically saw zero of the nudges — exactly the audience that benefits most.

This swaps the suppression for the stream choice:

  • Stream: hints + brand header go to stderr (`click.echo(..., err=True)`). Stdout stays machine-clean for pipes/redirects without the gate having to do it.
  • Gate: `hints_should_print()` drops the `sys.stdout.isatty()` check. Fires whenever `hints.enabled = true` (default) and `output.verbosity != "quiet"`.
  • Color/emoji: ANSI styling now keys off `sys.stderr.isatty()`, so captured logs stay plain.
  • `run_report.next_steps_hint` drops its parallel `pipe_mode` check for the same reason.

User-visible change

CI users who captured roar output without explicitly setting `hints.enabled = false` will start seeing hints on stderr. The escape valve hasn't changed:

```
roar config set hints.enabled false

or

roar config set output.verbosity quiet
```

Worth a release-note line.

Why this shape

  • Trading the gate for a stream change preserves pipeline cleanliness — `roar dag | jq …` and `roar show > out.txt` still work, by virtue of where the line goes, not by suppressing it conditionally.
  • Aligns with the git/npm/cargo convention of advisory output on stderr.
  • Agents and CI logs capture both streams; both audiences now see the nudges without needing config tweaks.

Test plan

  • `tests/unit/test_cli_format.py` (new) — direct stream-invariant tests for `hints_should_print`, `make_hint_printer`, and `print_brand_header`.
  • `test_init_hints_appear_in_non_tty_on_stderr` + `test_init_brand_header_appears_in_non_tty_on_stderr` — agent-visibility regression guards (assert presence on stderr, absence from stdout).
  • `test_next_steps_hint_appears_in_pipe_mode_for_agents_and_ci` (run_report).
  • Existing config-disable + quiet-suppression tests kept and passing.
  • 942 unit tests passing, 1 pre-existing skipped.
  • `ruff check .` + `ruff format --check .` + `mypy roar` all clean.

🤖 Generated with Claude Code

The advisory `hint:` lines and `🦖 roar <sub>` brand banner used to
self-suppress in non-TTY contexts (CI, redirected output, agent
capture). That kept stdout clean for pipelines but blinded agents like
Claude Code / Cursor — which capture roar's output programmatically
and benefit most from the `next: roar show …` style nudges.

Trade the stream choice for the suppression:

- **Stream**: hints and the brand header now go to **stderr** via
  `click.echo(..., err=True)`. stdout stays machine-clean for pipes
  and redirects without needing a TTY-suppression gate.
- **Gate**: `hints_should_print()` drops the `sys.stdout.isatty()`
  check. Hints fire whenever `hints.enabled = true` (default) and
  `output.verbosity != "quiet"`.
- **Color/emoji**: ANSI styling now keys off `sys.stderr.isatty()`,
  so captured logs stay plain (no escape sequences in CI output).
- **`run_report.next_steps_hint`** drops its parallel `pipe_mode`
  check — same reasoning. Its presenter already streams to stderr by
  default; the suppression was the only thing hiding the hint from
  agents.

Behavior change to surface in release notes: CI logs that captured
roar output without explicitly setting `hints.enabled = false` will
start showing hint lines on stderr. Escape valve is the same as
before — `roar config set hints.enabled false` or
`output.verbosity = quiet`.

Tests:

- `test_cli_format.py` (new) — locks in the stream invariant directly:
  hints go to stderr, brand header goes to stderr, gate honors
  config-disable and quiet-verbosity, and the TTY check is gone.
- `test_init_hints_appear_in_non_tty_on_stderr` — agent visibility
  regression guard. Asserts hints in `result.stderr` AND absent from
  `result.stdout`.
- `test_init_brand_header_appears_in_non_tty_on_stderr` — same shape
  for the banner.
- `test_next_steps_hint_appears_in_pipe_mode_for_agents_and_ci`
  (run_report) — repurposed from the old `_silent_in_pipe_mode` test;
  asserts the line shows up under pipe caps.
- Existing query-error test updated to use `result.stdout` (which
  excludes the now-stderr-bound banner) for its exact-match assertion.

942 unit tests passing, 1 pre-existing skipped. ruff + mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TrevorBasinger TrevorBasinger merged commit f1c198f into main May 19, 2026
12 checks passed
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.

3 participants