block/buzz · error · anyhow::Error

invalid relay URL {raw:?}: {error}

Error message

invalid relay URL {raw:?}: {error}

What it means

parse_configured_relay_url in desktop/src-tauri/src/mesh_llm/transport_policy.rs:62-89 validates each Iroh relay URL configured via the MESH_IROH_RELAYS environment variable before building the mesh transport. It first runs the raw string through url::Url::parse; when that fails it wraps the url::ParseError in an anyhow error with this message, embedding the offending raw value and the parser's reason (e.g. RelativeUrlWithoutBase, EmptyHost, InvalidPort). It fails at the very first stage of a three-stage check (URL parseability, scheme security, origin-only shape), so this specific error means the string was not a parseable absolute URL at all, rather than a valid URL rejected by policy.

Source

Thrown at desktop/src-tauri/src/mesh_llm/transport_policy.rs:64

pub(super) fn sdk_iroh_relay_config(mode: IrohRelayMode) -> (bool, Vec<String>) {
    match mode {
        IrohRelayMode::Disabled => (true, Vec::new()),
        IrohRelayMode::Default => (
            false,
            MESH_LLM_DEFAULT_RELAYS
                .iter()
                .map(|url| (*url).to_string())
                .collect(),
        ),
        IrohRelayMode::Custom(urls) => {
            (false, urls.into_iter().map(|url| url.to_string()).collect())
        }
    }
}

fn parse_configured_relay_url(raw: &str) -> anyhow::Result<RelayUrl> {
    let parsed = url::Url::parse(raw)
        .map_err(|error| anyhow::anyhow!("invalid relay URL {raw:?}: {error}"))?;
    let secure = parsed.scheme() == "https";
    let local_http = parsed.scheme() == "http"
        && parsed.host().is_some_and(|host| match host {
            url::Host::Domain(domain) => domain.eq_ignore_ascii_case("localhost"),
            url::Host::Ipv4(ip) => ip.is_loopback(),
            url::Host::Ipv6(ip) => ip.is_loopback(),
        });
    if !secure && !local_http {
        anyhow::bail!(
            "relay URL {raw:?} must use https (http is allowed only for loopback development)"
        );
    }
    if !parsed.username().is_empty()
        || parsed.password().is_some()
        || parsed.query().is_some()
        || parsed.fragment().is_some()
        || !matches!(parsed.path(), "" | "/")
    {

View on GitHub (pinned to 6c35e82bd5)

Solutions

  1. Read the {raw:?} in the message to find the exact offending entry, then correct that comma-separated entry in MESH_IROH_RELAYS to a full absolute URL such as https://relay.example.com.
  2. Ensure every entry includes an explicit scheme (https, or http only for loopback hosts like localhost/127.0.0.1/::1).
  3. Remove stray commas, whitespace, quotes, query strings, fragments, and credentials from the list; keep each entry a bare origin like https://host:port.
  4. If no custom relays are needed, unset MESH_IROH_RELAYS entirely (or set it to 0/default) to fall back to the built-in default relay set.
  5. Re-run and confirm startup passes; if it still fails, the message will move to the scheme or origin-only checks, indicating the URL now parses but violates policy.

Example fix

// before (MESH_IROH_RELAYS)
MESH_IROH_RELAYS=relay.example.com,https://backup.example.com

// after
MESH_IROH_RELAYS=https://relay.example.com,https://backup.example.com
Defensive patterns

Strategy: validation

Validate before calling

// Rust-side guard before reading MESH_IROH_RELAYS entries into transport config
fn validate_relay_list(raw_env: &str) -> Result<(), String> {
    for entry in raw_env.split(',').map(str::trim).filter(|e| !e.is_empty()) {
        match url::Url::parse(entry) {
            Ok(u) if matches!(u.scheme(), "https")
                || (u.scheme() == "http" && u.host_str().is_some_and(|h| h == "localhost" || h == "127.0.0.1")) => {}
            Ok(u) => return Err(format!("{entry:?}: scheme {:?} not allowed", u.scheme())),
            Err(e) => return Err(format!("{entry:?}: {e}")),
        }
    }
    Ok(())
}

Type guard

fn is_origin_only_url(raw: &str) -> bool {
    url::Url::parse(raw).map(|u| {
        u.username().is_empty()
            && u.password().is_none()
            && u.query().is_none()
            && u.fragment().is_none()
            && matches!(u.path(), "" | "/")
    }).unwrap_or(false)
}

Try / catch

// anyhow-based caller: propagate with context instead of unwrapping
let relays = std::env::var("MESH_IROH_RELAYS").ok();
let mode = iroh_relay_mode().with_context(|| {
    format!("invalid MESH_IROH_RELAYS (raw={relays:?}); expected comma-separated absolute https URLs")
})?;

Prevention

When it happens

Trigger: iroh_relay_mode() reads the MESH_IROH_RELAYS env var, splits it on commas, and calls parse_configured_relay_url per entry; url::Url::parse(raw) returns Err whenever an entry is not an absolute URL — missing scheme (e.g. "myrelay.example.com" without "https://"), a typo'd scheme ("htps://..."), an empty entry that survives trimming (",,"), stray whitespace inside a token, a bare IPv6 literal unbracketed, or any relative path like "/relay". The first malformed entry aborts the whole collect::<anyhow::Result<Vec<_>>>() with this error.

Common situations: Operators hand-editing the MESH_IROH_RELAYS env var forget the scheme prefix (writing relay.example.com instead of https://relay.example.com), copy a URL with a trailing comma or spaces, paste a URL containing a fragment or query that also trips later checks, or leave a stray comma in a comma-separated list; CI/dev containers inherit a partially-set or empty-list value of the variable.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of block/buzz@6c35e82bd5 (2026-09-13). Data as JSON: /api/errors/bcc14580af070421. Report an issue: GitHub.