mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
fix(cli): fall back to SelectSelector when kqueue can't watch stdin
On macOS with uv-managed cPython 3.11, the default kqueue selector cannot
register fd 0, so prompt_toolkit's loop.add_reader raises
OSError(EINVAL) ("[Errno 22] Invalid argument") from kqueue.control()
and the agent crashes immediately on startup (#5884, also reported in
#6393).
Probe KqueueSelector.register(0, EVENT_READ) before launching
prompt_toolkit. If it fails, install an event-loop policy that returns a
SelectorEventLoop backed by SelectSelector — select() works fine on
stdin in this Python build, so add_reader succeeds and the agent
launches normally.
Also extend the existing #6393 fallback handler to recognize EINVAL /
EBADF / "Invalid argument" so that any future selector failure on stdin
shows the friendly "reinstall Python via pyenv or Homebrew" guidance
instead of an opaque traceback.
Verified on macOS (Darwin 24.6.0) with uv-managed cPython 3.11.15: the
kqueue probe fails, the policy switch fires, and `hermes` launches
cleanly. No effect on platforms where kqueue can register fd 0.
This commit is contained in:
parent
4695d2716f
commit
eabd8c1fd1
1 changed files with 35 additions and 3 deletions
38
cli.py
38
cli.py
|
|
@ -13401,6 +13401,30 @@ class HermesCLI:
|
|||
self._print_exit_summary()
|
||||
return
|
||||
|
||||
# On macOS with uv-managed Python, kqueue's selector cannot register
|
||||
# fd 0, raising OSError(EINVAL) from kqueue.control() when prompt_toolkit
|
||||
# calls loop.add_reader (#6393). Probe kqueue and, if it can't watch
|
||||
# stdin, switch to a SelectSelector-backed event loop policy.
|
||||
if sys.platform == "darwin":
|
||||
try:
|
||||
import selectors as _selectors
|
||||
if hasattr(_selectors, "KqueueSelector"):
|
||||
_kq = _selectors.KqueueSelector()
|
||||
try:
|
||||
_kq.register(0, _selectors.EVENT_READ)
|
||||
_kq.unregister(0)
|
||||
finally:
|
||||
_kq.close()
|
||||
except (OSError, ValueError, KeyError):
|
||||
import asyncio as _aio_probe
|
||||
import selectors as _selectors
|
||||
|
||||
class _SelectEventLoopPolicy(_aio_probe.DefaultEventLoopPolicy):
|
||||
def new_event_loop(self):
|
||||
return _aio_probe.SelectorEventLoop(_selectors.SelectSelector())
|
||||
|
||||
_aio_probe.set_event_loop_policy(_SelectEventLoopPolicy())
|
||||
|
||||
# Run the application with patch_stdout for proper output handling
|
||||
try:
|
||||
with patch_stdout():
|
||||
|
|
@ -13421,12 +13445,20 @@ class HermesCLI:
|
|||
except (KeyError, OSError) as _stdin_err:
|
||||
# Catch selector registration failures from broken stdin (#6393)
|
||||
# and I/O errors from broken stdout during interrupt (#13710).
|
||||
if isinstance(_stdin_err, OSError) and getattr(_stdin_err, "errno", None) == errno.EIO:
|
||||
_errno = getattr(_stdin_err, "errno", None) if isinstance(_stdin_err, OSError) else None
|
||||
_msg = str(_stdin_err)
|
||||
if _errno == errno.EIO:
|
||||
pass # suppress broken-stdout I/O errors on interrupt (#13710)
|
||||
elif "is not registered" in str(_stdin_err) or "Bad file descriptor" in str(_stdin_err):
|
||||
elif (
|
||||
_errno in (errno.EINVAL, errno.EBADF)
|
||||
or "is not registered" in _msg
|
||||
or "Bad file descriptor" in _msg
|
||||
or "Invalid argument" in _msg
|
||||
):
|
||||
print(
|
||||
f"\nError: stdin is not usable ({_stdin_err}).\n"
|
||||
"This can happen with certain Python installations (e.g. uv-managed cPython on macOS).\n"
|
||||
"This can happen with certain Python installations (e.g. uv-managed cPython on macOS)\n"
|
||||
"where kqueue cannot register fd 0.\n"
|
||||
"Try reinstalling Python via pyenv or Homebrew, then re-run: hermes setup"
|
||||
)
|
||||
else:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue