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
- 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.
- Ensure every entry includes an explicit scheme (https, or http only for loopback hosts like localhost/127.0.0.1/::1).
- Remove stray commas, whitespace, quotes, query strings, fragments, and credentials from the list; keep each entry a bare origin like https://host:port.
- 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.
- 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
- Always include the scheme in relay URLs: https://host — never a bare hostname.
- Keep MESH_IROH_RELAYS a clean comma-separated origin list: no spaces, trailing commas, query strings, fragments, or credentials.
- Smoke-test the env value with a one-liner before deploy: parse each comma-split entry with url::Url::parse.
- Unset the variable entirely when defaults suffice rather than setting it to an empty or placeholder value.
- Document the accepted forms (https anywhere, http only for loopback) next to the deployment config.
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
- connect to test DB
- git pack cache path must be available
- BUZZ_RELAY_PRIVATE_KEY must be set when BUZZ_REQUIRE_AUTH_TO
- cannot derive community host from RELAY_URL; pass --host or
- database URL has path segment
AI-assisted analysis of block/buzz@6c35e82bd5 (2026-09-13).
Data as JSON: /api/errors/bcc14580af070421.
Report an issue: GitHub.