NanmiCoder/MediaCrawler · error · RuntimeError
Cannot find available port, tried {start_port} to {port-1}
Error message
Cannot find available port, tried {start_port} to {port-1} What it means
Raised by BrowserLauncher._find_available_port() after trying to bind 100 consecutive TCP ports starting at start_port on localhost and finding all occupied. The browser's remote-debugging port must be bindable, so an exhausted port range aborts launch.
Source
Thrown at tools/browser_launcher.py:117
if os.path.isfile(path) and os.access(path, os.X_OK):
paths.append(path)
return paths
def find_available_port(self, start_port: int = 9222) -> int:
"""
Find available port
"""
port = start_port
while port < start_port + 100: # Try up to 100 ports
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('localhost', port))
return port
except OSError:
port += 1
raise RuntimeError(f"Cannot find available port, tried {start_port} to {port-1}")
def launch_browser(self, browser_path: str, debug_port: int, headless: bool = False,
user_data_dir: Optional[str] = None) -> subprocess.Popen:
"""
Launch browser process
"""
# Basic launch arguments
args = [
browser_path,
f"--remote-debugging-port={debug_port}",
"--remote-debugging-address=0.0.0.0", # Allow remote access
"--no-first-run",
"--no-default-browser-check",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-features=TranslateUI",
"--disable-ipc-flooding-protection",View on GitHub (pinned to d6f7c5bb90)
Solutions
- Free the stuck ports: kill leftover chrome/chromium processes (pkill -f remote-debugging-port) or the services holding them
- Give each worker a distinct start port far apart (e.g. base 9222 + worker_id*200) so ranges never overlap
- Check occupancy first: ss -ltnp | grep -E '922[0-9]' to see what holds the range
- Raise the retry count or make start_port configurable so a busy block can be skipped
Example fix
// before
port = start_port
while port < start_port + 100:
...
// after — per-worker disjoint range
start_port = 9222 + worker_id * 200 # ranges never collide Defensive patterns
Strategy: fallback
Validate before calling
import socket
def port_free(port: int) -> bool:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(("localhost", port))
return True
except OSError:
return False
if not any(port_free(p) for p in range(start_port, start_port + 100)):
raise SystemExit(f"port block {start_port}-{start_port+99} exhausted; free ports or change start_port") Try / catch
try:
port = launcher._find_available_port(start_port)
except RuntimeError:
# fallback: widen the search range before giving up
port = launcher._find_available_port(start_port + 1000) Prevention
- Assign disjoint port ranges per parallel worker
- Reap zombie chrome processes between runs
- Check `ss -ltnp` for range occupancy before launching fleets
When it happens
Trigger: start_port is in a busy range — e.g. starting at 9222 while other Chrome instances, prior crashed crawler runs, or other services hold ports 9222-9321. Each iteration binds and releases, so only genuinely occupied (or permission-denied) ports advance the loop.
Common situations: Many parallel crawler workers each launching a browser with the same start port; zombie chrome processes from a previous run still holding debugging ports; Docker/CI environments where the ephemeral range or a same-range service occupies the block; ports below 1024 without privileges.
Related errors
- Crawler is already running
- File not found
- Not a file
- Cannot connect to existing browser on port {self.debug_port}
- Browser failed to start within {config.BROWSER_LAUNCH_TIMEOU
AI-assisted analysis of NanmiCoder/MediaCrawler@d6f7c5bb90 (2026-08-15).
Data as JSON: /api/errors/052cbdaaf8930ab5.
Report an issue: GitHub.