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/Popen with piped stdout, > file redirection, MCP stdio transport.
Happy to send a PR if helpful — wanted to surface the diagnosis first.
Summary
When
openkb.agent.query.run_query(..., stream=True)runs withsys.stdoutattached 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.Win32Outputand raises:This makes
openkb query— which always passesstream=True— unusable from any non-interactive parent on Windows (subprocess wrappers, capturedCI 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:
The crash fires as soon as the agent emits its first tool-call line, at the
_fmt(...)call site inopenkb/agent/query.py.Setting
NO_COLOR=1,TERM=dumb,PYTHONIOENCODING=utf-8, orCREATE_NO_WINDOWdoes not help —prompt_toolkiton Windows requires anactual console handle regardless of color/style settings.
Root cause
run_query(stream=True)insrc/openkb/agent/query.pyalready computes the right gate at line 133:…and correctly disables the Rich
Liveconsole whenuse_colorisFalse. But the prompt_toolkit_fmtpath is not gated — around line 211,_fmtis called unconditionally:_fmt(src/openkb/agent/chat.py:82) is a thin wrapper aroundprint_formatted_text, which on Windows constructsWin32Outputand demands a realconsole 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
openkbprogrammatically on Windows hits this. We hit it building an MCP server that shelled out toopenkb query; thefailure is total (no answer ever returned), and
openkb queryexposes no flag to reach the cleanstream=Falsebranch. Our workaround is to bypassthe 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_colorcheck to also gate the prompt_toolkit/Rich output, e.g. aroundquery.py:211:Or push the guard into
_fmtitself inchat.py:82so every caller benefits — fall back tosys.stdout.write(...)whennot sys.stdout.isatty().Either way, no
prompt_toolkitmachinery should run when stdout isn't a TTY.Related (optional)
openkb queryalways passesstream=True. Adding a--no-streamflag — or auto-disabling streaming whennot sys.stdout.isatty()— would letnon-interactive callers reach the clean non-streaming branch without library coupling.
Environment
subprocess.run/Popenwith piped stdout,> fileredirection, MCP stdio transport.Happy to send a PR if helpful — wanted to surface the diagnosis first.