n0-computer/iroh · error · ConnectWithOptsError
SelfConnect
SelfConnect
Error message
Connecting to ourself is not supported
What it means
An endpoint connection attempt targeted the endpoint's own EndpointId. A node cannot open a QUIC connection to itself through the same endpoint, so connect_with_opts rejects this immediately with ConnectWithOptsError::SelfConnect before any networking happens.
Solutions
- Filter out the local endpoint's own EndpointId before calling connect.
- Compare the target against endpoint.id() and skip/self-handle those entries.
- For loopback communication use an in-process channel instead of a QUIC connection.
- In peer-discovery code, exclude the node's own advertised addresses from the dial list.
Example fix
// before
endpoint.connect(peer.endpoint_id, alpn).await?;
// after
if peer.endpoint_id != endpoint.id() {
endpoint.connect(peer.endpoint_id, alpn).await?;
} Defensive patterns
Strategy: validation
Validate before calling
fn can_connect(ep: &Endpoint, target: EndpointId) -> bool { target != ep.id() } Type guard
fn remote_id(ep: &Endpoint, id: EndpointId) -> Option<EndpointId> { (id != ep.id()).then_some(id) } Try / catch
match endpoint.connect(id, alpn).await {
Err(e) if matches!(&e, ConnectWithOptsError::SelfConnect) => { /* skip self, not fatal */ }
other => other?,
} Prevention
- Filter self entries out of peer lists and discovery results.
- Never dial addresses your own node advertised.
- In tests, use two separate endpoints for the two sides.
- Keep a single source of truth for the local EndpointId.
When it happens
Trigger: Calling endpoint.connect(own_endpoint_id, alpn) (or passing an endpoint_addr whose endpoint_id equals self.id()) — e.g. dialing an address the node itself advertised, or a peer registry that returned this node's own entry.
Common situations: Bootstrapping nodes that add themselves to their peer list, tests reusing one endpoint for both sides, config files with a self-referencing relay/home address, discovering your own addresses via a discovery service and then dialing them.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
AI-assisted analysis of n0-computer/iroh@2b4de030ce (2026-09-08).
Data as JSON: /api/errors/ffd7c0c45d7f5367.
Report an issue: GitHub.
Appendix: source
Thrown at iroh/src/endpoint.rs:1122
options: ConnectOptions,
) -> Result<Connecting, ConnectWithOptsError> {
if self.is_closed() {
return Err(e!(ConnectWithOptsError::EndpointClosed));
}
let endpoint_addr: EndpointAddr = endpoint_addr.into();
let endpoint_id = endpoint_addr.id;
Span::current().record("remote", tracing::field::display(endpoint_id.fmt_short()));
if let BeforeConnectOutcome::Reject =
self.inner.hooks.before_connect(&endpoint_addr, alpn).await
{
return Err(e!(ConnectWithOptsError::LocallyRejected));
}
// Connecting to ourselves is not supported.
ensure!(endpoint_id != self.id(), ConnectWithOptsError::SelfConnect);
ensure!(!alpn.is_empty(), ConnectWithOptsError::InvalidAlpn);
event!(
target: "iroh::_events::conn::connecting",
tracing::Level::DEBUG,
remote_id = %endpoint_id.fmt_short(),
alpn = %String::from_utf8_lossy(alpn),
);
debug!(
relay_url = ?endpoint_addr.relay_urls().next().cloned(),
ip_addresses = ?endpoint_addr.ip_addrs().cloned().collect::<Vec<_>>(),
"connecting",
);
let mapped_addr = self.inner.resolve_remote(endpoint_addr).await??;
let transport_config = optionsView on GitHub (pinned to 2b4de030ce)