fix(cli): degrade _cprint to plain output when stdout has no console (fixes #65558)#65811
Open
hansai-art wants to merge 1 commit into
Open
fix(cli): degrade _cprint to plain output when stdout has no console (fixes #65558)#65811hansai-art wants to merge 1 commit into
hansai-art wants to merge 1 commit into
Conversation
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
tonydwb
reviewed
Jul 16, 2026
tonydwb
left a comment
There was a problem hiding this comment.
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
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.
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 withprompt_toolkit.output.win32.NoConsoleScreenBufferError. This made the-qflag unusable from any Windows automation/CI context.Fixes #65558.
Root cause (confirmed on Windows): when no prompt_toolkit
Applicationis running,_cprintcalls_pt_print, which builds a fresh pt output backend viacreate_output(). On Windows that'sWin32Output, which callsGetConsoleScreenBufferInfoon 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 ofcreate_output()has anisatty()fallback toPlainTextOutput; 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/_dhelpers already use). When anApplicationis running, its output backend already exists and works, so those paths (bg-threadrun_in_terminalscheduling, etc.) are left completely unchanged.The plain path also resolves the two related symptoms the reporter flagged, same "assume a real console" root cause:
UnicodeEncodeErroron 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 bareexcept).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 inlinetry: _pt_print except: print(text)fallback — which leaked raw ANSI and could drop unicode — is replaced by this.Scope / compatibility
_cprintchange. The app-running branches are byte-for-byte unchanged.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_cprintregression (pytest'scapsysstdout 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.pythat 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 -q→ 21 passed.python -m pytest tests/cli -k "cprint or output or print or terminal or replay or history" -q→ 161 passed.🤖 Generated with Claude Code
https://claude.ai/code/session_013b1XyXitAxV7phGmKWigJX