neondatabase/neon · error

invalid scheme {scheme}

Error message

invalid scheme {scheme}

What it means

pagebench basebackup accepts exactly three scheme values after lowercasing: postgresql and postgres (LibpqClient over the page protocol) and grpc (GrpcClient). Any other scheme reaches the catch-all match arm and returns 'invalid scheme {scheme}'. A common trap: 'localhost:6400' without a prefix parses successfully with 'localhost' as the scheme, producing this error rather than a connstring parse error.

Source

Thrown at pageserver/pagebench/src/cmd/basebackup.rs:175

    let mut work_senders = HashMap::new();
    let mut tasks = Vec::new();
    let scheme = match Url::parse(&args.page_service_connstring) {
        Ok(url) => url.scheme().to_lowercase().to_string(),
        Err(url::ParseError::RelativeUrlWithoutBase) => "postgresql".to_string(),
        Err(err) => return Err(anyhow!("invalid connstring: {err}")),
    };
    for &tl in &timelines {
        let (sender, receiver) = tokio::sync::mpsc::channel(1); // TODO: not sure what the implications of this are
        work_senders.insert(tl, sender);

        let client: Box<dyn Client> = match scheme.as_str() {
            "postgresql" | "postgres" => Box::new(
                LibpqClient::new(&args.page_service_connstring, tl, !args.no_compression).await?,
            ),
            "grpc" => Box::new(
                GrpcClient::new(&args.page_service_connstring, tl, !args.no_compression).await?,
            ),
            scheme => return Err(anyhow!("invalid scheme {scheme}")),
        };

        tasks.push(tokio::spawn(run_worker(
            client,
            Arc::clone(&start_work_barrier),
            receiver,
            Arc::clone(&all_work_done_barrier),
            Arc::clone(&live_stats),
        )));
    }

    let work_sender = async move {
        start_work_barrier.wait().await;
        loop {
            let (timeline, work) = {
                let mut rng = rand::rng();
                let target = all_targets.choose(&mut rng).unwrap();
                let lsn = target.lsn_range.clone().map(|r| rng.random_range(r));

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Prefix the target with postgresql:// (or postgres://), e.g. postgresql://postgres@localhost:6400
  2. Use grpc://host:port only when the pageserver's gRPC page_api listener is enabled and reachable
  3. Check the scheme spelling against the three accepted values

Example fix

# before
pagebench basebackup --page-service-connstring 'localhost:6400'   # scheme parses as 'localhost'
# after
pagebench basebackup --page-service-connstring 'postgresql://postgres@localhost:6400'
Defensive patterns

Strategy: validation

Validate before calling

const SUPPORTED: [&str; 3] = ["postgresql", "postgres", "grpc"];

fn resolve_scheme(connstring: &str) -> Option<String> {
    let scheme = match url::Url::parse(connstring) {
        Ok(u) => u.scheme().to_lowercase(),
        Err(url::ParseError::RelativeUrlWithoutBase) => "postgresql".to_string(),
        Err(_) => return None,
    };
    SUPPORTED.contains(&scheme.as_str()).then_some(scheme)
}

Type guard

fn is_supported_scheme(connstring: &str) -> bool {
    resolve_scheme(connstring).is_some()
}

Try / catch

match scheme.as_str() {
    "postgresql" | "postgres" => build_libpq_client().await,
    "grpc" => build_grpc_client().await,
    other => {
        eprintln!("scheme '{other}' not supported; use postgresql:// or grpc://");
        Err(anyhow!("invalid scheme {other}"))
    }
}

Prevention

When it happens

Trigger: Passing a bare host:port whose host name is a valid scheme token (localhost:6400, myhost:7000); using http://, https://, or tcp:// URLs copied from the management API; scheme typos such as postgressql:// or pg://.

Common situations: Forgetting the postgresql:// prefix; copy-pasting the HTTP management endpoint instead of the page_service endpoint; trying the experimental gRPC transport before enabling its listener on the pageserver.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/e9e91566385ff21e. Report an issue: GitHub.