studio: auto-find free port if requested port is in use

If the requested port (default 8000) is already in use, auto-
increment and try the next port, up to 20 attempts. Prints a
message like "Port 8000 is in use, using port 8001 instead".

Previously, if port 8000 was busy, uvicorn would fail with
"[Errno 98] address already in use" and the studio would not
start. Now it gracefully finds the next free port.

Uses socket.bind() to check availability before starting uvicorn.
Cross-platform (Linux, macOS, Windows).
This commit is contained in:
Daniel Han 2026-03-14 09:18:21 +00:00
commit 928868f07d

View file

@ -69,6 +69,29 @@ def _resolve_external_ip() -> str:
return "0.0.0.0"
def _is_port_free(host: str, port: int) -> bool:
"""Check if a port is available for binding."""
import socket
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((host, port))
return True
except OSError:
return False
def _find_free_port(host: str, start: int, max_attempts: int = 20) -> int:
"""Find a free port starting from `start`, trying up to max_attempts ports."""
for offset in range(max_attempts):
candidate = start + offset
if _is_port_free(host, candidate):
return candidate
raise RuntimeError(
f"Could not find a free port in range {start}-{start + max_attempts - 1}"
)
def run_server(
host: str = "0.0.0.0",
port: int = 8000,
@ -80,7 +103,7 @@ def run_server(
Args:
host: Host to bind to
port: Port to bind to
port: Port to bind to (auto-increments if in use)
frontend_path: Path to frontend build directory (optional)
silent: Suppress startup messages
"""
@ -99,6 +122,13 @@ def run_server(
# Create all standard directories on startup
ensure_studio_directories()
# Auto-find free port if requested port is in use
if not _is_port_free(host, port):
original_port = port
port = _find_free_port(host, port)
if not silent:
print(f"Port {original_port} is in use, using port {port} instead")
# Setup frontend if path provided
if frontend_path:
if setup_frontend(app, frontend_path):