zeroclaw-labs/zeroclaw · error · anyhow::Error
Port {port} is already in use, so the gateway could not star
Error message
Port {port} is already in use, so the gateway could not start. What it means
The gateway HTTP listener could not bind because the requested address (host:port) is already taken by another process. The code detects this by walking the error chain with is_addr_in_use_error (downcasting to std::io::Error with kind AddrInUse) and replaces the raw io error with an actionable message that includes a restart hint port from available_gateway_restart_hint_port.
Source
Thrown at src/main.rs:8132
// can self-respawn after the listener is released. Must mirror the same
// call in the Daemon branch.
zeroclaw_runtime::restart::record_launch();
// Standalone gateway (no daemon supervisor): pass None for reload_tx so
// /admin/reload returns 503 with a clear "no supervisor; restart
// manually" message, None for tui_registry (no TUI socket), and None
// for canvas_store so the gateway falls back to its own default.
let result = Box::pin(gateway::run_gateway(
host, port, config, tx, None, None, None, None, None, None,
))
.await;
// Self-respawn after the listener is released, if an in-app upgrade
// requested it. No-op when no respawn was requested or on supervised
// restart modes.
zeroclaw_runtime::restart::respawn_if_requested();
match result {
Err(err) if is_addr_in_use_error(&err) => {
let restart_port = available_gateway_restart_hint_port(host, port);
anyhow::bail!(
"{}",
gateway_addr_in_use_message(host, port, &default_host, default_port, restart_port)
);
}
other => other,
}
}
#[cfg(not(feature = "gateway"))]
#[allow(clippy::unused_async)]
async fn run_gateway_if_enabled(
_host: &str,
_port: u16,
_config: zeroclaw::config::Config,
_tx: Option<tokio::sync::broadcast::Sender<serde_json::Value>>,
) -> anyhow::Result<()> {
anyhow::bail!("Gateway feature is not enabled. Rebuild with --features gateway")
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Free the port: stop the other process (`lsof -i :<port>` / `ss -ltnp` to find it)
- Use the restart hint port the error message suggests, or change gateway port in the config and restart
- If it is a respawn race after a restart, wait a moment and start again — the old listener releases the socket
- Set a distinct port per instance when running multiple gateways
Example fix
# before port = 8080 # already bound by another process # after port = 8081 # or kill the holder: kill $(lsof -t -i :8080)
Defensive patterns
Strategy: validation
Validate before calling
// Probe the bind before handing the port to the gateway:
use tokio::net::TcpListener;
match TcpListener::bind((host, port)).await {
Ok(_) => { /* port free; drop the probe listener immediately */ }
Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
// pick another port or surface a clear message before starting
}
Err(e) => return Err(e.into()),
} Type guard
// Reuse the same chain-walking check the source uses:
fn is_addr_in_use_error(err: &anyhow::Error) -> bool {
err.chain().any(|c| c.downcast_ref::<std::io::Error>()
.map_or(false, |io| io.kind() == std::io::ErrorKind::AddrInUse))
} Try / catch
// Catch on the serve call, branch on AddrInUse via the chain (as main.rs does), // then either retry with the suggested hint port or fail with the actionable message.
Prevention
- Reserve one port per gateway instance in config management
- Set SO_REUSEADDR-equivalent behavior via quick shutdown handling on restart
- Add a boot-time port probe in orchestration scripts
- On supervised restarts, wait for the old process to release the socket before respawning
When it happens
Trigger: Starting zeroclaw while another zeroclaw instance, a respawned child, or any unrelated process (dev server, proxy) already listens on the configured gateway host:port. Also happens when a supervised restart races the old process shutting down.
Common situations: Two gateway instances configured with the same port; a previous instance that did not fully exit (orphan/respawn race); another service (nginx, node dev server) occupying the port; docker port mappings colliding.
Related errors
- QQ gateway request failed ({status}): {err}
- channel does not support room creation
- elicitation returned unknown choice const: {s}
- purge_namespace not supported by this memory backend
- git channel has no repositories to poll; set `repos` or gran
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/f72dc2c06e3a9faa.
Report an issue: GitHub.