Repository navigation
run_query(stream=True) crashes on Windows when stdout is not a console (NoConsoleScreenBufferError from prompt_toolkit _fmt) #34
Description
Summary
When openkb.agent.query.run_query(..., stream=True) runs with sys.stdout attached to anything other than a real Windows console (a pipe, a file,
a captured subprocess stream, an MCP stdio transport), the streaming path calls _fmt(...) → prompt_toolkit.shortcuts.print_formatted_text(...),
which constructs a prompt_toolkit.output.win32.Win32Output and raises:
prompt_toolkit.output.win32.NoConsoleScreenBufferError: No Windows console found. Are you running cmd.exe?
This makes openkb query — which always passes stream=True — unusable from any non-interactive parent on Windows (subprocess wrappers, captured
CI output, MCP servers, redirected output).
Repro (Windows 11, openkb 0.1.3, Python 3.11)
Either of these reproduces it; both detach stdout from a console:
openkb query "anything" *> output.log
python -c "import subprocess; subprocess.run(['openkb','query','hi'], stdout=subprocess.PIPE, check=True)"The crash fires as soon as the agent emits its first tool-call line, at the _fmt(...) call site in openkb/agent/query.py.
Setting NO_COLOR=1, TERM=dumb, PYTHONIOENCODING=utf-8, or CREATE_NO_WINDOW does not help — prompt_toolkit on Windows requires an
actual console handle regardless of color/style settings.
Root cause
run_query(stream=True) in src/openkb/agent/query.py already computes the right gate at line 133:
use_color = sys.stdout.isatty() and not os.environ.get("NO_COLOR", "")…and correctly disables the Rich Live console when use_color is False. But the prompt_toolkit _fmt path is not gated — around line 211,
_fmt is called unconditionally:
_fmt(style, ("class:tool", _format_tool_line(name, args) + "\n"))_fmt (src/openkb/agent/chat.py:82) is a thin wrapper around print_formatted_text, which on Windows constructs Win32Output and demands a real
console handle even when no color attributes are being emitted. So even with use_color = False, this single line still crashes.
Why it matters downstream
Any process wrapping openkb programmatically on Windows hits this. We hit it building an MCP server that shelled out to openkb query; the
failure is total (no answer ever returned), and openkb query exposes no flag to reach the clean stream=False branch. Our workaround is to bypass
the CLI and call openkb.agent.query.run_query(..., stream=False) directly, which couples us to a non-public underscore-adjacent API.
Suggested fix (smallest)
Reuse the existing use_color check to also gate the prompt_toolkit/Rich output, e.g. around query.py:211:
if use_color:
_fmt(style, ("class:tool", _format_tool_line(name, args) + "\n"))
else:
sys.stdout.write(_format_tool_line(name, args) + "\n")
sys.stdout.flush()Or push the guard into _fmt itself in chat.py:82 so every caller benefits — fall back to sys.stdout.write(...) when not sys.stdout.isatty().
Either way, no prompt_toolkit machinery should run when stdout isn't a TTY.
Related (optional)
openkb query always passes stream=True. Adding a --no-stream flag — or auto-disabling streaming when not sys.stdout.isatty() — would let
non-interactive callers reach the clean non-streaming branch without library coupling.
Environment
- OS: Windows 11 Pro (10.0.26200)
- Python: 3.11 (uv tool install)
- openkb: 0.1.3
- Reproduces under:
subprocess.run/Popenwith piped stdout,> fileredirection, MCP stdio transport.
Happy to send a PR if helpful — wanted to surface the diagnosis first.
Activity
KylinMountain commentedon May 11, 2026
@Litash
Thanks for the detailed diagnosis — confirmed end-to-end.
Going to push the isatty() guard into _fmt itself in chat.py so every caller falls back to plain sys.stdout.write(...) when stdout isn't a TTY. Will also auto-disable stream=True in openkb query for non-TTY stdout, rather than adding a flag.
PR welcome if you've already got one.
fix(query): make openkb query safe for non-TTY stdout (closes Vectify…
fix(query): make openkb query safe for non-TTY stdout (closes #34) (#45)