Description
In CLI mode, the interactive prompt locks up the terminal completely whenever
Hermes's lazy dependency installer fires. Observed via vision_analyze on a
fresh install where Pillow was not present. The CLI becomes fully unresponsive:
no keyboard input is accepted, Ctrl-C does nothing, and the quit commands do not
work. Recovery requires killing the process from another terminal. Reproduces
every time.
The on-screen state when wedged:
Feature 'tool.vision' requires: Pillow==12.2.0
Install into the active venv now? [Y/n]
Root cause (confirmed from source)
This is NOT pip prompting. It is Hermes's own lazy-install confirmation in
tools/lazy_deps.py. The prompt is emitted with a bare builtin input():
# tools/lazy_deps.py (around lines 459-465)
if prompt and sys.stdin.isatty() and sys.stdout.isatty():
spec_list = ", ".join(missing)
try:
answer = input(
f"\nFeature {feature!r} requires: {spec_list}\n"
f"Install into the active venv now? [Y/n] "
).strip().lower()
except (EOFError, KeyboardInterrupt):
answer = "n"
In CLI mode sys.stdin and sys.stdout ARE TTYs, so the isatty() guard
passes and the branch runs. But the interactive CLI runs prompt_toolkit, which
owns stdin. A raw blocking input() competes with prompt_toolkit's event loop
for the same stdin: keystrokes are consumed by prompt_toolkit, the input()
call never receives the answer, and the whole session wedges with no way to
answer, interrupt, or quit.
This is the same failure mode as the closed confirmation-prompt hangs
#23694 / #24343 (daemon-thread input() vs prompt_toolkit stdin). Those
fixes covered Hermes slash-command prompts; the lazy_deps.py prompt is a
separate code path that was not covered, so it still hangs.
The isatty() check is the wrong gate. It detects "attached to a terminal" but
not "another reader (prompt_toolkit) already owns stdin." Any lazy-install
trigger in an interactive CLI session hits this (vision/Pillow is just the
common one).
Steps to reproduce
- Fresh install where a lazy feature dependency is missing (e.g. Pillow absent,
so tool.vision is unsatisfied).
- Launch
hermes in interactive CLI mode.
- Trigger the feature (call
vision_analyze on any image).
- Hermes prints
Install into the active venv now? [Y/n] and blocks on
input(). The CLI accepts no input: cannot answer, cannot Ctrl-C, cannot
quit. Recover only by killing the process from another terminal.
Expected
When running under the interactive CLI, the confirmation must be routed through
prompt_toolkit (the same mechanism the slash-command confirmation fix uses)
rather than a bare input(), OR the prompt should be suppressed in favor of the
security.allow_lazy_installs config gate when prompt_toolkit owns stdin. The
session must stay answerable, interruptible, and quittable.
Actual
Bare input() blocks under prompt_toolkit and never receives the keystroke.
Total input lockup; external kill is the only recovery. Reproduces every time.
Workaround
Pre-install the dependency so the lazy path never triggers, e.g.
~/.hermes/hermes-agent/venv/bin/python -m pip install "Pillow==12.2.0".
(My earlier pip install --no-input suggestion does NOT help, because pip is
not the thing prompting; Hermes is.)
Environment
- Hermes Agent v0.15.1 (2026.5.29)
- macOS 26.5.1 (build 25F80), Apple Silicon
- Python 3.11.15 (Hermes runtime)
Related
Description
In CLI mode, the interactive prompt locks up the terminal completely whenever
Hermes's lazy dependency installer fires. Observed via
vision_analyzeon afresh install where Pillow was not present. The CLI becomes fully unresponsive:
no keyboard input is accepted, Ctrl-C does nothing, and the quit commands do not
work. Recovery requires killing the process from another terminal. Reproduces
every time.
The on-screen state when wedged:
Root cause (confirmed from source)
This is NOT pip prompting. It is Hermes's own lazy-install confirmation in
tools/lazy_deps.py. The prompt is emitted with a bare builtininput():In CLI mode
sys.stdinandsys.stdoutARE TTYs, so theisatty()guardpasses and the branch runs. But the interactive CLI runs prompt_toolkit, which
owns stdin. A raw blocking
input()competes with prompt_toolkit's event loopfor the same stdin: keystrokes are consumed by prompt_toolkit, the
input()call never receives the answer, and the whole session wedges with no way to
answer, interrupt, or quit.
This is the same failure mode as the closed confirmation-prompt hangs
#23694 / #24343 (daemon-thread
input()vs prompt_toolkit stdin). Thosefixes covered Hermes slash-command prompts; the
lazy_deps.pyprompt is aseparate code path that was not covered, so it still hangs.
The
isatty()check is the wrong gate. It detects "attached to a terminal" butnot "another reader (prompt_toolkit) already owns stdin." Any lazy-install
trigger in an interactive CLI session hits this (vision/Pillow is just the
common one).
Steps to reproduce
so
tool.visionis unsatisfied).hermesin interactive CLI mode.vision_analyzeon any image).Install into the active venv now? [Y/n]and blocks oninput(). The CLI accepts no input: cannot answer, cannot Ctrl-C, cannotquit. Recover only by killing the process from another terminal.
Expected
When running under the interactive CLI, the confirmation must be routed through
prompt_toolkit (the same mechanism the slash-command confirmation fix uses)
rather than a bare
input(), OR the prompt should be suppressed in favor of thesecurity.allow_lazy_installsconfig gate when prompt_toolkit owns stdin. Thesession must stay answerable, interruptible, and quittable.
Actual
Bare
input()blocks under prompt_toolkit and never receives the keystroke.Total input lockup; external kill is the only recovery. Reproduces every time.
Workaround
Pre-install the dependency so the lazy path never triggers, e.g.
~/.hermes/hermes-agent/venv/bin/python -m pip install "Pillow==12.2.0".(My earlier
pip install --no-inputsuggestion does NOT help, because pip isnot the thing prompting; Hermes is.)
Environment
Related
input()vsprompt_toolkit stdin race. Those fixed slash-command prompts;
lazy_deps.pyis an uncovered sibling path.
input-lockup symptom, different trigger.
blocks the loop" class, different surface.