Skip to content

[BUG] find_available_port probe-then-rebind races; create_server has no retry → EADDRINUSE with parallel Neovim instances (regression in #282) #283

@PMassicotte

Description

@PMassicotte

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:

  1. 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).
  2. 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

  1. Open Neovim instance A; the integration claims a port (e.g. 48811).
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions