ruvnet/RuView · error

cannot bind HTTP listener on {http_addr}: {e}

Error message

cannot bind HTTP listener on {http_addr}: {e}

What it means

tokio TcpListener::bind failed for the HTTP API address {http_bind}:{http_port} in calibrate-serve. io::Error causes: AddrInUse (another HTTP service or a second calibrate-serve on that port), permission denied on privileged ports, or a --http-bind address the host does not own. This is a startup failure, distinct from runtime serve errors.

Source

Thrown at v2/crates/wifi-densepose-cli/src/calibrate_api.rs:363

    let state = ApiState { cmd_tx, status, window, fs_hz: 15.0, enroll };
    let mut app = build_router(state);

    // Optional bearer auth — required before any non-loopback exposure.
    if let Some(token) = args.token.clone() {
        app = app.layer(axum::middleware::from_fn_with_state(token, require_bearer));
        eprintln!("[calibrate-serve] bearer auth ENABLED");
    } else if args.http_bind != "127.0.0.1" && args.http_bind != "localhost" {
        eprintln!(
            "[calibrate-serve] WARNING: bound to {} with NO --token — anyone on the network can drive calibration",
            args.http_bind
        );
    }

    let http_addr = format!("{}:{}", args.http_bind, args.http_port);
    let listener = tokio::net::TcpListener::bind(&http_addr)
        .await
        .map_err(|e| anyhow::anyhow!("cannot bind HTTP listener on {http_addr}: {e}"))?;
    eprintln!("[calibrate-serve] HTTP API on http://{http_addr}  (GET / for the route list)");

    axum::serve(listener, app)
        .await
        .map_err(|e| anyhow::anyhow!("HTTP server error: {e}"))?;
    Ok(())
}

// ---------------------------------------------------------------------------
// Ingest task — owns the UDP socket and the optional active recorder
// ---------------------------------------------------------------------------

struct ActiveSession {
    recorder: CalibrationRecorder,
    room_id: String,
    tier: String,
    started: Instant,
    deadline: Instant,

View on GitHub (pinned to 4685618388)

Solutions

  1. Stop the occupying process or choose a different --http-port (ss -tlnp to find it)
  2. Use a port of 1024 or higher, or grant CAP_NET_BIND_SERVICE for low ports
  3. Set --http-bind to an address that exists on the host (127.0.0.1 for local, the NIC IP for LAN)
  4. When binding non-loopback, pass --token — the code warns that an unauthenticated LAN-bound API lets anyone drive calibration

Example fix

// before
$ ruview calibrate-serve --http-bind 0.0.0.0 --http-port 80   # permission denied / AddrInUse

// after
$ ruview calibrate-serve --http-bind 0.0.0.0 --http-port 8080 --token <secret>
Defensive patterns

Strategy: validation

Validate before calling

fn tcp_port_free(bind: &str, port: u16) -> bool {
    std::net::TcpListener::bind((bind, port)).is_ok()
}

Try / catch

match tokio::net::TcpListener::bind(&http_addr).await {
    Ok(listener) => listener,
    Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
        return Err(anyhow::anyhow!("http://{http_addr} already in use — stop the other service or change --http-port"));
    }
    Err(e) => return Err(anyhow::anyhow!("cannot bind HTTP listener on {http_addr}: {e}")),
}

Prevention

When it happens

Trigger: Another web service already listening on the chosen port; --http-port below 1024 as non-root; --http-bind set to an address not present on the host.

Common situations: Collisions with dev servers on common ports like 8080; container port maps where the host port is taken; typo'd bind address; running two calibration API instances on one host.

Related errors


AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16). Data as JSON: /api/errors/5d6417a1d9416b8b. Report an issue: GitHub.