hatoo/oha · error · anyhow::Error

Both --cert and --key must be specified

Error message

Both --cert and --key must be specified

What it means

Mutual TLS client authentication requires both a client certificate (--cert) and its private key (--key). The match on (cert, key) handles Some/Some and None/None; any mixed combination bails. The comment notes clap's requires rules normally make this unreachable, but the code defends defensively.

Solutions

  1. Always pass --cert and --key together when using mTLS.
  2. Pass neither if client authentication is not needed.
  3. Verify the option-construction path isn't setting only one field.

Example fix

// before
oha --cert client.pem https://example.com
// after
oha --cert client.pem --key client-key.pem https://example.com
Defensive patterns

Strategy: validation

Validate before calling

if opt_cert.is_some() != opt_key.is_some() {
    return Err("--cert and --key must be provided together".into());
}

Try / catch

match run(opts).await {
    Err(e) if e.to_string().contains("Both --cert and --key must be specified") => {
        eprintln!("mTLS requires both --cert and --key");
    }
    Err(e) => return Err(e),
    Ok(v) => v,
}

Prevention

When it happens

Trigger: Programmatic construction of run options bypassing clap validation, providing exactly one of --cert or --key.

Common situations: Rarely hit via normal CLI use because clap enforces the pairing; can appear with generated configs or custom option builders that skip clap parsing.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of hatoo/oha@4efba2d113 (2026-09-09). Data as JSON: /api/errors/f715bc9cebd2b378. Report an issue: GitHub.

Appendix: source

Thrown at src/main.rs:587

        }
        (true, false) => hickory_resolver::config::LookupIpStrategy::Ipv4Only,
        (false, true) => hickory_resolver::config::LookupIpStrategy::Ipv6Only,
        (true, true) => hickory_resolver::config::LookupIpStrategy::Ipv4AndIpv6,
    };
    let (config, mut resolver_opts) = system_resolv_conf()?;
    resolver_opts.ip_strategy = ip_strategy;
    let resolver = hickory_resolver::Resolver::builder_with_config(
        config,
        hickory_resolver::net::runtime::TokioRuntimeProvider::default(),
    )
    .with_options(resolver_opts)
    .build()?;
    let cacert = opts.cacert.as_deref().map(std::fs::read).transpose()?;
    let client_auth = match (opts.cert, opts.key) {
        (Some(cert), Some(key)) => Some((std::fs::read(cert)?, std::fs::read(key)?)),
        (None, None) => None,
        // Not possible because of clap requires
        _ => anyhow::bail!("Both --cert and --key must be specified"),
    };

    let url = url.into_owned();
    let client = Arc::new(client::Client {
        request_generator: RequestGenerator {
            url_generator,
            https: url.scheme() == "https",
            version: http_version,
            aws_config,
            method,
            headers,
            body_generator,
            http_proxy: if opts.proxy.is_some() && url.scheme() == "http" {
                Some(Proxy {
                    headers: proxy_headers.clone(),
                    version: proxy_http_version,
                })
            } else {

View on GitHub (pinned to 4efba2d113)