Skip to content

fix(cli): never deadlock on lazy-dep prompt + make Pillow a core dep (#40490)#40901

Merged
teknium1 merged 4 commits into
mainfrom
salvage/40490-lazy-dep-prompt
Jun 7, 2026
Merged

fix(cli): never deadlock on lazy-dep prompt + make Pillow a core dep (#40490)#40901
teknium1 merged 4 commits into
mainfrom
salvage/40490-lazy-dep-prompt

Conversation

@teknium1

@teknium1 teknium1 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Lazy dependency installs can no longer deadlock the interactive CLI, and the one dep that actually mattered (Pillow) now ships in the base install so the lazy path rarely fires at all.

Root cause: tools/lazy_deps.py:ensure() emitted a bare input() confirmation gated only by isatty(). Under the interactive CLI, prompt_toolkit owns stdin, so the keystrokes route to its event loop and input() blocks forever — no answer, no Ctrl-C, no quit. Fixes #40490 (P1).

Changes

  • tools/lazy_deps.py: detect a running prompt_toolkit app (get_app_or_none() + is_running) and skip the prompt when it owns the terminal — proceed, since the install is already gated by security.allow_lazy_installs. (Contributor commit, @kyssta-exe.)
  • tools/vision_tools.py, tools/transcription_tools.py: pass prompt=False on the two ensure() call sites that defaulted to prompt=True — the only tool-call paths that could reach the blocking prompt. Every other call site already did this. Makes the deadlock-capable input() branch unreachable from any tool path.
  • pyproject.toml: promote Pillow==12.2.0 from the [vision] extra to a core dependency. It drives the image-shrink path at vision-embed time; without it an oversized image (>5 MB / >8000px) bakes into immutable history and bricks the session on Anthropic's non-retryable 400. Pure-wheel, no system libs. [vision] becomes a no-op back-compat alias; the tool.vision lazy entry stays as a fallback for stripped installs.
  • uv.lock: regenerated — Pillow moves to an unconditional core dep, keeps uv sync --locked green.

Validation

Before After
vision_analyze on large image, fresh install, CLI bare input() deadlocks (kill from another terminal) Pillow already present (core dep); lazy path skipped
ensure(prompt=False) under running prompt_toolkit + isatty n/a (path was prompt=True) install proceeds, input() never called
allow_lazy_installs=false raises FeatureUnavailable unchanged, no prompt
targeted tests 160/160 pass (test_vision_tools, test_vision_native_fast_path, test_lazy_deps)

E2E verified all branches with real imports + a simulated running prompt_toolkit app (no mocks of the resolution path).

Salvages #40579 — the pt-guard commit was cherry-picked onto current main with @kyssta-exe's authorship preserved; the prompt=False hardening, Pillow promotion, and lockfile are follow-ups on top. Closes #40490.

Infographic

lazy-dep prompt deadlock fixed

kyssta-exe and others added 4 commits June 6, 2026 18:29
…l paths

The vision (Pillow) and faster-whisper STT tool paths were the only
ensure() call sites that defaulted to prompt=True, so they could fire a
blocking input() confirmation mid-session. Every other call site already
passes prompt=False. Under the interactive CLI prompt_toolkit owns stdin,
so that input() deadlocks the terminal (#40490). The install is already
gated by security.allow_lazy_installs, so the prompt was redundant
consent anyway. This makes the deadlock-capable input() branch
unreachable from any tool-call path.
Pillow drives the byte/pixel image-shrink path that runs at vision-embed
time. Without it, an oversized image (>5 MB or >8000px) bakes into
immutable history and bricks the session on Anthropic's non-retryable
400. It's a pure-wheel dep with no system-lib requirement for the codecs
we use, so there's no reason to gate it behind an extra + a mid-session
lazy install (the install that deadlocked the CLI under prompt_toolkit,
#40490). Every install — base, [all], packagers — now ships it.

The [vision] extra becomes a no-op back-compat alias so existing
'pip install hermes-agent[vision]' invocations still resolve. The
tool.vision lazy-deps entry is kept as a belt-and-suspenders fallback for
stripped/source-build installs.
Pillow moves from the [vision] extra marker to an unconditional core
dependency. Keeps 'uv sync --locked' green.
@teknium1 teknium1 requested a review from a team June 7, 2026 01:33
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: salvage/40490-lazy-dep-prompt vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9966 on HEAD, 9965 on base (🆕 +1)

🆕 New issues (1):

Rule Count
unresolved-import 1
First entries
tools/lazy_deps.py:469: [unresolved-import] unresolved-import: Cannot resolve imported module `prompt_toolkit.application.current`

✅ Fixed issues: none

Unchanged: 5168 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard tool/vision Vision analysis and image generation dependencies Pull requests that update a dependency file labels Jun 7, 2026
@teknium1 teknium1 merged commit 5b55f4f into main Jun 7, 2026
26 checks passed
@teknium1 teknium1 deleted the salvage/40490-lazy-dep-prompt branch June 7, 2026 01:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard dependencies Pull requests that update a dependency file P1 High — major feature broken, no workaround tool/vision Vision analysis and image generation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: CLI input locks up unrecoverably on lazy-dep install prompt (lazy_deps.py uses bare input() under prompt_toolkit)

3 participants