Skip to content

fix(cli): fall back to SelectSelector when kqueue can't watch stdin on macOS - #20948

Closed
outdoorsea wants to merge 1 commit into
NousResearch:mainfrom
outdoorsea:fix/macos-uv-kqueue-stdin
Closed

fix(cli): fall back to SelectSelector when kqueue can't watch stdin on macOS#20948
outdoorsea wants to merge 1 commit into
NousResearch:mainfrom
outdoorsea:fix/macos-uv-kqueue-stdin

Conversation

@outdoorsea

Copy link
Copy Markdown

What does this PR do?

On macOS with uv-managed cPython 3.11, the default kqueue selector cannot register fd 0, so when prompt_toolkit calls loop.add_reader(0, ...) during app.run(), kqueue.control() raises OSError: [Errno 22] Invalid argument (EINVAL) and the agent crashes immediately on startup. Reproduces 100% on the affected Python builds.

This PR fixes it by probing KqueueSelector.register(0, EVENT_READ) once at startup; if it fails, an event-loop policy is installed that returns a SelectorEventLoop backed by SelectSelector instead. select() works fine on stdin in this Python build, so add_reader succeeds and the agent launches normally. Platforms where kqueue can register fd 0 are unaffected — the probe succeeds and the default policy is used.

It also extends the existing #6393 fallback handler (which was added when this same family of bugs was first reported) to recognize EINVAL / EBADF / "Invalid argument". Previously the handler only matched "is not registered" and "Bad file descriptor", so an EINVAL leaking through (which is the actual macOS+kqueue failure mode) re-raised as a raw traceback after Goodbye! ⚕ was printed — exactly the symptom in #5884.

Related Issue

Fixes #5884
Re-fixes the underlying cause originally reported in #6393

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • cli.py — before launching prompt_toolkit, probe KqueueSelector.register(0, ...) on macOS. On failure, install a DefaultEventLoopPolicy subclass whose new_event_loop() returns SelectorEventLoop(SelectSelector()). asyncio.run() (called inside app.run()) then picks up the SelectSelector-backed loop and add_reader works.
  • cli.py — extend the post-app.run() except (KeyError, OSError) handler to additionally match errno in (EINVAL, EBADF) and "Invalid argument" so the friendly "reinstall Python via pyenv or Homebrew" guidance is shown if any future selector failure on stdin slips past the probe.

How to Test

Reproducing the original crash requires uv-managed cPython on macOS. With that build:

  1. python -c "import selectors; kq = selectors.KqueueSelector(); kq.register(0, selectors.EVENT_READ)" — confirms the OS-level failure (OSError: [Errno 22] Invalid argument).
  2. Without this PR: hermes crashes with the traceback in [Setup]: OSError: [Errno 22] Invalid argument:[prompt_toolkit crash] #5884.
  3. With this PR: hermes launches normally; piping /exit produces clean shutdown.

I verified all three on Darwin 24.6.0 with cpython-3.11.15-macos-aarch64-none (the exact build affected in #5884). On Python builds where the kqueue probe passes (Homebrew, pyenv, system Python, Linux), the new code path is a no-op.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — did not run the full suite locally; please rely on CI
  • I've added tests for my changes — did not add a test: the kqueue+stdin failure mode is platform- and build-specific (only fires on uv-managed cPython on macOS) and is genuinely awkward to fake portably. Happy to add one if a maintainer can suggest a reasonable mocking approach
  • I've tested on my platform: macOS Sequoia (Darwin 24.6.0) with uv-managed cPython 3.11.15

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A (internal workaround, no user-facing surface change)
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — the workaround is gated on sys.platform == "darwin" and only triggers when kqueue actually fails to register fd 0; no effect on Linux/Windows or on macOS Pythons where kqueue works.
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

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 (NousResearch#5884, also reported in
NousResearch#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 NousResearch#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.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard duplicate This issue or pull request already exists labels May 7, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #13251 — this is the 8th+ competing PR for the macOS kqueue EINVAL stdin registration crash (#6393/#5884). Multiple approaches already exist: #12111 (PollSelector fallback), #13251 (errno-based suppression), #15720 (rebind to /dev/tty), #19501, #20482, etc.

@teknium1

Copy link
Copy Markdown
Contributor

Merged via #26077 (commit d3d5916). Your commit was cherry-picked onto current main with your authorship preserved in git log. Thanks for the thorough fix — the kqueue probe was the key piece that none of the sibling PRs had. Closing this in favor of the salvage PR.

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 duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Setup]: OSError: [Errno 22] Invalid argument:[prompt_toolkit crash]

4 participants