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
- Free the port or pick another (lsof -i :PORT / netstat) and update both the config and the provider-registered redirect URI
- Use a high port (e.g. 49152+) that needs no privileges and is unlikely to collide
- Confirm the host resolves — prefer 127.0.0.1 over a hostname
- 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
- Check the port is free before starting the flow (lsof -i :PORT)
- Use high, uncommon ports
- Avoid privileged ports <1024 unless running with capabilities
- Update the provider-registered redirect whenever you change ports
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
- Failed to bind port
- models endpoint returned
- Bridge register: server returned
- start_bridge: bridge is not active
- Token exchange failed
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)