Summary
Since 9ecf9f3 (merge of #282), running a second Neovim instance can fail to start the WebSocket server with:
[ClaudeCode] [init] [ERROR] Failed to start Claude Code server: Failed to listen on port 48811: EADDRINUSE: address already in use
[ClaudeCode] [command] [ERROR] ClaudeCodeAdd: Claude Code integration is not running.
This is a regression. Running multiple Neovim instances in parallel (each with its own integration) worked reliably before this commit.
Root cause
lua/claudecode/server/tcp.lua was rewritten in #282. find_available_port now probes a candidate port by binding a throwaway test socket, closing it, and returning the port:
local test_server = vim.loop.new_tcp()
local success = test_server:bind("127.0.0.1", port)
test_server:close()
if success then
return port
end
create_server then creates a new TCP handle and binds + listen()s on that same port. Two problems:
- TOCTOU race / unreliable probe. The check and the use are separate sockets. Because libuv sets
SO_REUSEADDR, the throwaway bind() can succeed even when another process is already listening on the port, so the real listen() is what fails with EADDRINUSE — matching the error text ("Failed to listen on port ...", the listen step, not bind).
- No retry/fallback. The port is selected once at
server/init.lua:95; on bind/listen failure create_server returns an error and gives up rather than trying the next port. So a single collision is a hard failure.
Reproduction
- Open Neovim instance A; the integration claims a port (e.g. 48811).
- Open Neovim instance B; if its scan lands on a port already held by another listener, B fails with
EADDRINUSE and the integration never starts.
Suggested fix
Either:
- Hold the probe socket open through to
listen() (don't close-and-rebind), or
- Loop in
create_server: on bind/listen EADDRINUSE, advance to the next candidate from find_available_port and retry across the range before giving up.
Environment
- claudecode.nvim at
9ecf9f3
- Neovim 0.12.2
- Linux
Summary
Since
9ecf9f3(merge of #282), running a second Neovim instance can fail to start the WebSocket server with:This is a regression. Running multiple Neovim instances in parallel (each with its own integration) worked reliably before this commit.
Root cause
lua/claudecode/server/tcp.luawas rewritten in #282.find_available_portnow probes a candidate port by binding a throwaway test socket, closing it, and returning the port:create_serverthen creates a new TCP handle and binds +listen()s on that same port. Two problems:SO_REUSEADDR, the throwawaybind()can succeed even when another process is already listening on the port, so the reallisten()is what fails withEADDRINUSE— matching the error text ("Failed to listen on port ...", the listen step, not bind).server/init.lua:95; on bind/listen failurecreate_serverreturns an error and gives up rather than trying the next port. So a single collision is a hard failure.Reproduction
EADDRINUSEand the integration never starts.Suggested fix
Either:
listen()(don't close-and-rebind), orcreate_server: on bind/listenEADDRINUSE, advance to the next candidate fromfind_available_portand retry across the range before giving up.Environment
9ecf9f3