fix(cli): never deadlock on lazy-dep prompt + make Pillow a core dep (#40490)#40901
Merged
Conversation
…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.
Contributor
🔎 Lint report:
|
| 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 bareinput()confirmation gated only byisatty(). Under the interactive CLI, prompt_toolkit owns stdin, so the keystrokes route to its event loop andinput()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 bysecurity.allow_lazy_installs. (Contributor commit, @kyssta-exe.)tools/vision_tools.py,tools/transcription_tools.py: passprompt=Falseon the twoensure()call sites that defaulted toprompt=True— the only tool-call paths that could reach the blocking prompt. Every other call site already did this. Makes the deadlock-capableinput()branch unreachable from any tool path.pyproject.toml: promotePillow==12.2.0from 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; thetool.visionlazy entry stays as a fallback for stripped installs.uv.lock: regenerated — Pillow moves to an unconditional core dep, keepsuv sync --lockedgreen.Validation
vision_analyzeon large image, fresh install, CLIinput()deadlocks (kill from another terminal)ensure(prompt=False)under running prompt_toolkit + isattyprompt=True)input()never calledallow_lazy_installs=falsetest_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=Falsehardening, Pillow promotion, and lockfile are follow-ups on top. Closes #40490.Infographic