Kuberwastaken/claurst · error · anyhow::Error

Failed to bind OAuth callback listener on

Error message

Failed to bind OAuth callback listener on {}:{}: {}

What it means

The local TCP listener for the OAuth callback could not bind to the host:port derived from the redirect_uri. Typically the port is already in use (another auth session or a stale listener holding it), or the host is not bindable on this machine.

Solutions

  1. Free the port or pick another (lsof -i :PORT / netstat) and update both the config and the provider-registered redirect URI
  2. Use a high port (e.g. 49152+) that needs no privileges and is unlikely to collide
  3. Confirm the host resolves — prefer 127.0.0.1 over a hostname
  4. If binding a low port is required, grant cap_net_bind_service rather than running as root

Example fix

// before
let redirect_uri = "http://127.0.0.1:80/callback";
// after
let redirect_uri = "http://127.0.0.1:8090/callback";
Defensive patterns

Strategy: validation

Validate before calling

async fn port_free(port: u16) -> bool {
    tokio::net::TcpListener::bind(("127.0.0.1", port)).await.is_ok()
}

Try / catch

match bind_callback_listener(&uri).await {
    Ok((l, h, p)) => { /* proceed */ }
    Err(e) if e.to_string().contains("address already in use") => eprintln!("port busy; pick another"),
    Err(e) => eprintln!("bind failed: {e}"),
}

Prevention

When it happens

Trigger: bind_callback_listener on a port already in use, a privileged port (<1024) without root, an unresolvable/blocked host name, or IPv6-only host strings the resolver can't handle.

Common situations: Another instance of the tool or a dev server occupies the port (EADDRINUSE); running inside Docker/K8s without the port published; firewall blocking loopback binding of a nonstandard port; registering port 80/443 with the provider and running unprivileged.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/f5647dacb2d81022. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/oauth.rs:233

    redirect_uri: &str,
) -> anyhow::Result<(TcpListener, String, String)> {
    let redirect_url = url::Url::parse(redirect_uri)
        .map_err(|e| anyhow::anyhow!("Failed to parse redirect URI '{}': {}", redirect_uri, e))?;
    let host = redirect_url
        .host_str()
        .ok_or_else(|| anyhow::anyhow!("Redirect URI '{}' is missing host", redirect_uri))?
        .to_string();
    let port = redirect_url
        .port_or_known_default()
        .ok_or_else(|| anyhow::anyhow!("Redirect URI '{}' is missing port", redirect_uri))?;
    let callback_path = if redirect_url.path().is_empty() {
        "/callback".to_string()
    } else {
        redirect_url.path().to_string()
    };
    let listener = TcpListener::bind((host.as_str(), port))
        .await
        .map_err(|e| anyhow::anyhow!("Failed to bind OAuth callback listener on {}:{}: {}", host, port, e))?;

    Ok((listener, host, callback_path))
}

async fn wait_for_authorization_code(
    listener: TcpListener,
    host: &str,
    callback_path: &str,
    expected_state: Option<&str>,
) -> anyhow::Result<String> {
    let (mut socket, _) = tokio::time::timeout(Duration::from_secs(180), listener.accept())
        .await
        .map_err(|_| anyhow::anyhow!("Timeout waiting for OAuth callback"))?
        .map_err(|e| anyhow::anyhow!("Failed to accept OAuth callback connection: {}", e))?;

    let (reader, mut writer) = socket.split();
    let mut reader = BufReader::new(reader);
    let mut request_line = String::new();

View on GitHub (pinned to b0637c97ec)