headroomlabs-ai/headroom · error · RuntimeError
No available port found in range {start_port}-{end_port - 1}
Error message
No available port found in range {start_port}-{end_port - 1} What it means
When starting the wrap proxy, Headroom probes a range of ports (start_port up to start_port + max_attempts, capped at 65536) by attempting to bind each one. Ports failing with EADDRINUSE (already bound) or EACCES (privileged/reserved on Linux/Windows) are skipped as unusable; any other OSError (e.g. EADDRNOTAVAIL) propagates immediately. If every port in the range is busy or privileged, this RuntimeError is raised.
Source
Thrown at headroom/cli/wrap.py:601
return None
def _find_available_port(start_port: int, max_attempts: int = 100) -> int:
"""Find first available port >= start_port via socket.bind probe.
Skips ports with EADDRINUSE (busy) and EACCES (reserved on Windows,
privileged on Linux) — both indicate the port can't be bound here.
Other OS errors (EADDRNOTAVAIL) propagate immediately.
Raises RuntimeError when no port is found in range.
"""
end_port = min(start_port + max_attempts, 65536)
for port in range(start_port, end_port):
error = _port_bind_error(port)
if error is None:
return port
if error.errno not in (errno.EADDRINUSE, errno.EACCES):
raise error
raise RuntimeError(f"No available port found in range {start_port}-{end_port - 1}")
def _get_log_path() -> Path:
"""Get path for proxy log file."""
from headroom import paths as _paths
log_dir = _paths.log_dir()
log_dir.mkdir(parents=True, exist_ok=True)
return log_dir / "proxy.log"
def _get_proxy_stdio_log_path() -> Path:
"""Get path for dedicated proxy stdio capture."""
return _get_log_path().with_name("proxy-stdio.log")
def _start_proxy(
port: int,View on GitHub (pinned to 322425c43b)
Solutions
- Free the occupied ports: find and kill stale proxies, e.g. lsof -i :4000-4099 then kill <pid>, or pkill -f headroom
- Pass an explicit port in an empty range, e.g. headroom wrap claude --port 8787
- Raise the attempt count / use a different start port so the scan covers a wider or freer range
- If EACCES on every port, rerun from an unprivileged high port range (>1024) or grant the process permission to bind
Example fix
# before headroom wrap claude # scans default range, all busy -> RuntimeError # after pkill -f 'headroom.*proxy' # clear stale proxies headroom wrap claude --port 8787
Defensive patterns
Strategy: validation
Validate before calling
import socket
def find_free_port(start: int, attempts: int = 100) -> int | None:
for port in range(start, min(start + attempts, 65536)):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(("127.0.0.1", port))
except OSError:
continue
return port
return None
assert find_free_port(4000) is not None, "No free port in wrap range; kill stale proxies first" Try / catch
try:
start_proxy()
except RuntimeError as e:
if "No available port found in range" in str(e):
# retry with an explicit, verified-free port
port = find_free_port(20000)
start_proxy(port=port)
else:
raise Prevention
- Kill stale headroom proxies before wrapping: pkill -f 'headroom.*proxy'
- Pass an explicit --port in a high, rarely-used range
- Check occupancy first: lsof -i :<port-range>
When it happens
Trigger: Running `headroom wrap <tool>` (or any code path that calls the port-finding helper) on a machine where all ports in the scanned range — typically starting near the requested port, e.g. 4000-4099 — are already bound by other processes, or where the process lacks permission to bind any of them (EACCES on all candidates).
Common situations: Many stale Headroom proxy processes left running from previous sessions exhaust the default range; dev machines crowded with other local servers (Vite, Docker, databases) occupying the scanned ports; running as a low-privilege user where the chosen range intersects reserved ports; a low ulimit or socket exhaustion causing binds to fail.
Related errors
- Proxy exited with code {proc.returncode}: {tail}
- Proxy process exited unexpectedly.
- Error: {e}
- Error: 'claude' not found in PATH.
- Error: 'copilot' not found in PATH.
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/47b673da503e5424.
Report an issue: GitHub.