diff --git a/claude-code-extension/src/lib.rs b/claude-code-extension/src/lib.rs index 7044084..0811fc6 100644 --- a/claude-code-extension/src/lib.rs +++ b/claude-code-extension/src/lib.rs @@ -88,7 +88,7 @@ impl Extension for ClaudeCodeExtension { "debug": true, "websocket": { "host": "127.0.0.1", - "portRange": [10000, 65535] + "portRange": [59790, 59799] }, "auth": { "generateTokens": true diff --git a/claude-code-server/src/main.rs b/claude-code-server/src/main.rs index 8c2b8d6..ed8ac44 100644 --- a/claude-code-server/src/main.rs +++ b/claude-code-server/src/main.rs @@ -36,13 +36,13 @@ enum Mode { }, /// Run as standalone WebSocket server for Claude Code CLI Websocket { - /// WebSocket server port (default: 59791) + /// WebSocket server port (default: auto-select from 59790-59799) #[arg(long, short)] port: Option, }, /// Run both LSP and WebSocket servers Hybrid { - /// WebSocket server port (default: 59791) + /// WebSocket server port (default: auto-select from 59790-59799) #[arg(long, short)] port: Option, /// Worktree root path diff --git a/claude-code-server/src/websocket.rs b/claude-code-server/src/websocket.rs index ec61449..534cfb8 100644 --- a/claude-code-server/src/websocket.rs +++ b/claude-code-server/src/websocket.rs @@ -51,48 +51,47 @@ pub async fn run_websocket_server_with_notifications( ) -> Result<()> { info!("Starting WebSocket server..."); - // Use fixed port or provided port, default to 59792 - let port = port.unwrap_or(59792); - - // Clean up any existing lock files for this port - cleanup_existing_lock_file(port).await?; - - // Create new lock file - let auth_token = Uuid::new_v4().to_string(); - create_lock_file(port, worktree.clone(), &auth_token).await?; - - // Start WebSocket server with proper error handling - let addr = format!("127.0.0.1:{}", port); - - // Try to bind to the port, with retry logic - let listener = match TcpListener::bind(&addr).await { - Ok(listener) => { - info!("WebSocket server listening on {}", addr); - listener - } - Err(e) => { - error!("Failed to bind to port {}: {}", port, e); - info!("Attempting to force cleanup and retry..."); - - // Try to cleanup and retry once - cleanup_existing_lock_file(port).await?; - - // Wait a moment for the port to be released - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - + // Port range for multi-instance support (59790-59799, max 10 instances) + const PORT_RANGE_START: u16 = 59790; + const PORT_RANGE_END: u16 = 59799; + + let (listener, port) = if let Some(port) = port { + // Explicit port requested - use it directly + let addr = format!("127.0.0.1:{}", port); + let listener = TcpListener::bind(&addr) + .await + .map_err(|e| anyhow!("Failed to bind to requested port {}: {}", port, e))?; + info!("WebSocket server listening on {}", addr); + (listener, port) + } else { + // No port specified - find first available port in range + let mut bound = None; + for p in PORT_RANGE_START..=PORT_RANGE_END { + let addr = format!("127.0.0.1:{}", p); match TcpListener::bind(&addr).await { Ok(listener) => { - info!("Successfully bound to port {} after cleanup", port); - listener + info!("WebSocket server listening on {} (auto-selected)", addr); + bound = Some((listener, p)); + break; } - Err(e2) => { - error!("Failed to bind to port {} even after cleanup: {}", port, e2); - return Err(anyhow!("Port {} is unavailable: {}", port, e2)); + Err(_) => { + info!("Port {} in use, trying next...", p); } } } + bound.ok_or_else(|| { + anyhow!( + "No available port in range {}-{} (max 10 instances)", + PORT_RANGE_START, + PORT_RANGE_END + ) + })? }; + // Create new lock file + let auth_token = Uuid::new_v4().to_string(); + create_lock_file(port, worktree.clone(), &auth_token).await?; + // Setup graceful shutdown handler let port_for_cleanup = port; tokio::spawn(async move {