ErrLookupBackground articles › "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures

"Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures

"Invalid URL", "URL cannot be empty", and "couldn't parse URL" errors happen before any network traffic: a library's URL parser (WHATWG new URL, Go's net/url, Rust's Url::parse, Ruby's URI) rejected the string you handed it. This guide explains the common triggers — missing scheme, empty or whitespace-only values, unencoded spaces and control characters, empty hosts — and how to fix and prevent them.

Distilled from 125 documented records across 50 repositories.

Background

The invalid-url family is produced at the request-construction layer, before any socket is opened. Every HTTP client must turn a string into a structured URL object — JavaScript's WHATWG URL constructor, Go's url.Parse or http.NewRequestWithContext, Rust's reqwest::Url::parse, Ruby's URI.parse combined with Addressable — and each of those parsers has a strict grammar. When the string violates it (or fails a scheme/host policy check layered on top of the parse), the library fails fast with an error like "Invalid URL", "URL cannot be empty", or a wrapped parse error such as "invalid server URL %q: %w". Because request construction performs no I/O, the error fires immediately and deterministically: the same input produces the same error every time, with no network, timeout, or server involved.

These checks exist for two reasons beyond basic parsing. First, a malformed URL that slipped through parsing would otherwise surface later as a confusing DNS failure, 404, or timeout deep inside the request path — several libraries (pnpm's registry alias validation, jaeger's OTLP proxy registration, Tailscale's client constructors) validate explicitly to move that failure to startup or configuration time. Second, some checks are security guards: an empty host would send a request to the local machine (siyuan's WebFetch treats this as an SSRF risk), absolute-form paths could retarget a proxied request past Deno's --allow-net permission check, and postal's AddressGuard treats a hostless URL as a blocked destination rather than attempting a connection.

From the caller's side, the error usually points at a configuration value or user input rather than code logic. The recurring culprits across the 50 repositories are remarkably consistent: a base URL or endpoint stored without its https:// scheme ("api.example.com/v1", "localhost:8000"), an empty string from an unset environment variable or config key, whitespace or control characters picked up from copy-paste or YAML parsing, unencoded spaces in a path, or a host segment accidentally dropped by string concatenation ("https:/example.com", "http:///path"). The error message often embeds the offending value — sometimes quoted with %q to make invisible characters visible — or, deliberately, omits it when the URL may contain credentials (GitNexus's LLM base URL validation).

The family varies across libraries in where it draws the line. What one parser rejects outright, another accepts: Go's url.Parse is lenient and lets scheme-less hosts and empty strings through, pushing those failures to later scheme/host checks, while the WHATWG URL constructor requires an absolute URL and throws on anything relative without a base. Empty-host detection is another fork: some libraries reject at parse time, others (siyuan, postal) as a distinct post-parse guard. And libraries that must accept relative URLs (gemini-cli's OAuth endpoints with allowRelative) fail only when the combination of relative string and base URI is unparseable. So the same input string can produce this error in one library and pass into a different failure mode in another.

Common causes

What usually fixes it

Go deeper

Documented occurrences

…and 105 more across the corpus — use search.

Honest provenance: generated on 2026-09-01 from AI-assisted analysis of the linked records. See how records are made.