Skip to content

Claude Code no longer dies at launch on macOS with "EINVAL: invalid argument, kqueue" - #166

Open
DrewBradfordXYZ wants to merge 1 commit into
AgentSystemLabs:mainfrom
DrewBradfordXYZ:fix/pane-tty-kqueue
Open

DrewBradfordXYZ wants to merge 1 commit into
AgentSystemLabs:mainfrom
DrewBradfordXYZ:fix/pane-tty-kqueue

Conversation

@DrewBradfordXYZ

Copy link
Copy Markdown
Contributor

Since #130, every Claude Code agent on macOS exits at launch with error: EINVAL: invalid argument, kqueue (exit 1). Agents now start again, and the Iris autostart fix from #130 still holds.

Contents: 🐛 Symptom · 🔍 Cause · ✅ Fix · 🧪 Proof · ⚠️ Risk

🐛 Symptom

  • Any new or resumed Claude agent dies about 0.2s after it starts, and the pane shows Bun's stack trace:
    error: EINVAL: invalid argument, kqueue
          at emitError (node:events:50:13)
          at emitErrorNT (internal:streams/destroy:88:12)
    Bun v1.4.3 (macOS arm64)
    
  • The row goes to exited.
  • Reproduced on unmodified main (a00b760) with Claude Code 2.1.296, on a fresh daemon with no user rc files.

🔍 Cause

  • Keep rc autostart hooks from swallowing agent launches #130 gives the login shell /dev/null as stdin while the rc files run, so Iris-style autostarts leave the shell alone.
  • The launch line then runs exec </dev/tty before the agent command.
  • macOS kqueue refuses /dev/tty (EVFILT_READ on the controlling-terminal alias returns EINVAL). Claude Code is a Bun binary that watches stdin with kqueue, so it throws on startup.
    • The same CLI, given the PTY's own device (/dev/ttys00N) as stdin, starts normally.
  • Keep rc autostart hooks from swallowing agent launches #130's tests passed because the e2e test's claude is a shell function that only checks [ -t 0 ], and /dev/tty passes that.

✅ Fix

  • The stdin shim names the PTY before the rc files run:
    • It records NEBULA_PANE_TTY=$(tty), falling back to /dev/tty if tty fails, then hands the shell /dev/null as before.
  • The launch line reopens stdin from that path, not /dev/tty, and unsets the variable before the agent runs, so no child inherits it.
  • Unchanged:
    • rc files still see a non-TTY stdin, so Iris still skips its autostart.
    • aliases and functions still resolve the command word.
    • TERM / COLORTERM are restated as before.
  • The variable name lives in nebula_core::env beside the others (PANE_TTY).

🧪 Proof

  • The test change is small: two lines added to the existing Iris e2e test.

    • create_agent_survives_rc_autostart_that_execs_on_tty_stdin now also records the fake claude's stdin device, and asserts it is not /dev/tty.
    • Checked against the old /dev/tty line: it fails (left: "/dev/tty"). It passes with the fix.
  • Real-CLI A/B, using a throwaway e2e test (not committed) that runs nebula daemon with SHELL=/bin/zsh, launches the real claude 2.1.296, attaches and reads the pane for 20s:

    Build Result
    main a00b760 EINVAL: invalid argument, kqueue, SessionExited exit_code=1 ~0.2–0.6s after create
    main + this PR Claude's folder-trust screen renders; still running after 20s
cargo test -p nebula-daemon --lib login_shell             4 passed
cargo test -p nebula --test e2e_pty -- create_agent       5 passed
cargo clippy -p nebula-daemon -p nebula-core --all-targets   clean

⚠️ Risk

🟢 Low.

  • Scope: only the PTY launch wrapper changes. CLI probes don't go through it.
  • tty fallback: if tty can't name stdin, the shim falls back to /dev/tty, which is the old behaviour.
  • Linux: behaviour is the same, since reopening the device path is equivalent to /dev/tty there.

🤖 Generated with Claude Code

…rgument, kqueue"

AgentSystemLabs#130 gave the login shell /dev/null as stdin while the rc files run, then
reopened /dev/tty before the agent command. macOS kqueue refuses /dev/tty,
and Claude Code (Bun) watches stdin with kqueue, so every Claude agent
exited 1 on startup. The stdin shim now records the PTY's own device path
in NEBULA_PANE_TTY before the rc files run, and the launch line reopens
stdin from that path (then unsets it). rc files still see a non-TTY stdin,
so the Iris autostart fix from AgentSystemLabs#130 holds; its e2e test now also asserts
the agent's stdin is not /dev/tty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant