Skip to content

fix(cli): degrade _cprint to plain output when stdout has no console (fixes #65558)#65811

Open
hansai-art wants to merge 1 commit into
NousResearch:mainfrom
hansai-art:fix/win-noconsole-cprint
Open

fix(cli): degrade _cprint to plain output when stdout has no console (fixes #65558)#65811
hansai-art wants to merge 1 commit into
NousResearch:mainfrom
hansai-art:fix/win-noconsole-cprint

Conversation

@hansai-art

Copy link
Copy Markdown

What & why

On Windows, hermes chat -q "<prompt>" with piped/redirected stdout — the documented non-interactive path, and exactly what any orchestrator does to capture the response (stdio: pipe) — crashed with prompt_toolkit.output.win32.NoConsoleScreenBufferError. This made the -q flag unusable from any Windows automation/CI context.

Fixes #65558.

Root cause (confirmed on Windows): when no prompt_toolkit Application is running, _cprint calls _pt_print, which builds a fresh pt output backend via create_output(). On Windows that's Win32Output, which calls GetConsoleScreenBufferInfo on the process's stdout handle — a pipe, not a console — and raises. I reproduced it directly: _pt_print(ANSI(...)) with a non-tty stdout → NoConsoleScreenBufferError. (The POSIX branch of create_output() has an isatty() fallback to PlainTextOutput; the win32 branch has none.)

Fix

Suggested option 1 from the issue (in-tree, no upstream dependency): detect a non-console stdout up front and emit plain text instead of the pt renderer — but only in the no-app path, where a fresh backend would otherwise be constructed. This mirrors the isatty() gate prompt_toolkit uses on POSIX (and that this file's own _b/_d helpers already use). When an Application is running, its output backend already exists and works, so those paths (bg-thread run_in_terminal scheduling, etc.) are left completely unchanged.

The plain path also resolves the two related symptoms the reporter flagged, same "assume a real console" root cause:

  • ANSI escapes leaking into captured output → stripped via a CSI regex.
  • UnicodeEncodeError on box-drawing / braille characters under a legacy Windows code page (cp1252) → re-encode through stdout's encoding with replacement, so they degrade to ? instead of raising (previously silently dropped by a bare except).

New helpers: _stdout_is_console(), _plain_output(), and _emit_without_running_app() (shared by the no-app and the pt-import-failure branches). The old inline try: _pt_print except: print(text) fallback — which leaked raw ANSI and could drop unicode — is replaced by this.

Scope / compatibility

  • Only the no-app and pt-import-failure branches of _cprint change. The app-running branches are byte-for-byte unchanged.
  • When stdout is a console, behavior is identical to before (pt renderer, colors intact).

Testing

tests/cli/test_cli_cprint_no_console.py (8 new): console detection (incl. isatty() raising), ANSI + cursor/erase stripping, the cp1252 encoding-safe fallback (via a fake cp1252 stream), the end-to-end no-console _cprint regression (pytest's capsys stdout is already non-tty — the exact broken path — so it asserts no crash + ANSI stripped), and that a real console still routes through prompt_toolkit.

Updated 3 existing tests in test_cprint_bg_thread.py that assert the pt-routing path, pinning _stdout_is_console -> True (the console presence they implicitly assumed); the no-console behavior is covered by the new file.

  • python -m pytest tests/cli/test_cli_cprint_no_console.py tests/cli/test_cprint_bg_thread.py -q21 passed.
  • python -m pytest tests/cli -k "cprint or output or print or terminal or replay or history" -q161 passed.
  • Authored and reproduced on Windows.

🤖 Generated with Claude Code

https://claude.ai/code/session_013b1XyXitAxV7phGmKWigJX

On Windows, `hermes chat -q "<prompt>"` with piped/redirected stdout — the
documented non-interactive path, and exactly what any orchestrator does to
capture output (`stdio: pipe`) — crashed with
`prompt_toolkit.output.win32.NoConsoleScreenBufferError`. When no pt
Application is running, `_cprint` calls `_pt_print`, which builds a fresh pt
output backend; on Windows `Win32Output` queries the console screen buffer on
what is actually a pipe handle and raises, aborting the run. Fixes NousResearch#65558.

Reproduced on Windows: `_pt_print(ANSI(...))` with a non-tty stdout raises
NoConsoleScreenBufferError.

Fix (issue's suggested option 1 — in-tree, no upstream dependency): detect a
non-console stdout up front (mirrors the `isatty()` gate prompt_toolkit uses on
POSIX, and that `_b`/`_d` already use here) and emit plain text instead of the
pt renderer, only in the no-app path where a fresh backend would be built. A
running app already has a working output backend, so those paths are untouched.

The plain path also fixes two related symptoms the reporter noted, both from
the same "assume a real console" root cause:
- literal ANSI escapes leaking into captured output → stripped;
- `UnicodeEncodeError` on box-drawing/braille chars under a legacy code page
  (cp1252) → re-encode through stdout's encoding with replacement, so they
  degrade to `?` instead of raising (previously they were silently dropped).

New helpers: `_stdout_is_console()`, `_plain_output()`, and
`_emit_without_running_app()` (shared by the no-app and pt-import-failure
branches).

Tests: tests/cli/test_cli_cprint_no_console.py (8) — console detection incl.
isatty-raises, ANSI/cursor stripping, the cp1252 encoding-safe fallback, and
the end-to-end no-console `_cprint` regression (pytest's capsys stdout is
already non-tty, i.e. the exact broken path) plus the console path still using
prompt_toolkit. Updated 3 existing tests in test_cprint_bg_thread.py that
asserted the pt-routing path to pin `_stdout_is_console -> True` (their
implicit assumption). `tests/cli -k "cprint or output or print or terminal or
replay or history"` → 161 passed. Authored and reproduced on Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013b1XyXitAxV7phGmKWigJX
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 16, 2026

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Degrades _cprint to plain output when stdout has no console. Clean fix for SIGWINCH issue. No security concerns.


Reviewed by Hermes Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

prompt_toolkit crashes with NoConsoleScreenBufferError on Windows when hermes chat -q stdout is piped (non-interactive/automated use)

3 participants