Skip to content

run_query(stream=True) crashes on Windows when stdout is not a console (NoConsoleScreenBufferError from prompt_toolkit _fmt) #34

@Litash

Description

@Litash

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions