Skip to content

Claude Code crashes with Bun kqueue EINVAL when launched from fzf execute() #153

@zhfeng

Description

@zhfeng

Description

When using the fzf interactive picker (gtr cdctrl-a) to launch Claude Code, it crashes with a Bun kqueue error:

Bun v1.3.11 (macOS arm64)
239 |       throw @makeErrorWithCode(118, "options.flush", "boolean", flush);
240 |     this.flush = flush;
241 |   }
242 |   if (this.start = start, this.pos = @undefined, this.bytesWritten = 0, start !== @undefined)
243 |     validateInteger(start, "start", 0), this.pos = start;
244 |     this[kWriteStreamFastPath] = fd ? Bun.file(fd).writer() : !0, this._write = underscoreWriteFast, this._writev = @undefined, this.write = writeFast;
                                                               ^
error: EINVAL: invalid argument, kqueue

Environment

  • macOS arm64 (Darwin 24.6.0)
  • Claude Code 2.1.72 (bundles Bun v1.3.11)
  • git-gtr 2.4.0
  • fzf 0.70.0

Root Cause

fzf's execute() action runs the command with stdout/stderr connected through pipes rather than the terminal. Claude Code is built on Bun, which uses macOS kqueue to watch file descriptors. When Bun's WriteStream tries to register fzf's pipe FDs with kqueue, it fails with EINVAL.

Running gtr ai <branch> directly from the shell works fine — the issue only occurs when launched through fzf's execute() binding in the shell integration (init.sh).

Workaround

Wrapping the claude command with script -q /dev/null in adapters/ai/claude.sh fixes the issue by allocating a proper pseudo-terminal:

-  (cd "$path" && "$claude_cmd" "$@")
+  # Use 'script' to allocate a proper PTY for Claude/Bun,
+  # preventing kqueue EINVAL errors when launched from fzf execute()
+  (cd "$path" && script -q /dev/null "$claude_cmd" "$@")

script gives Claude/Bun real TTY file descriptors for stdin/stdout/stderr, bypassing the pipe FDs that kqueue can't handle.

Notes

  • This likely affects any Bun-based AI tool launched via the fzf ctrl-a binding
  • Other approaches tried that did not work:
    • Increasing ulimit -n
    • Closing inherited FDs (3-9 or all above stderr)
    • Redirecting stdin/stdout/stderr to /dev/tty (causes TypeError: undefined is not an object (evaluating 'process.stderr.fd'))
    • Using fzf's become() instead of execute()
  • The script wrapper may also be useful as a general pattern for other terminal-based AI tools in the adapter system

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions