ducaale/xh · error

Request body (from stdin) and request data (key=value)…

Error message

Request body (from stdin) and request data (key=value) cannot be mixed. Pass --ignore-stdin to ignore standard input.

What it means

The CLI detected piped stdin content and also request data items (key=value), which would require mixing a raw stdin body with form fields — impossible in a single request. It errors and suggests --ignore-stdin.

Solutions

  1. Pass --ignore-stdin to discard piped stdin and send only the key=value request items.
  2. Remove the key=value items and send the stdin body alone (e.g. with a Content-Type header).
  3. Move the stdin content into a request item such as field@file instead of piping.
  4. Run from an interactive terminal or explicitly redirect stdin from /dev/null.

Example fix

// before
echo '{"a":1}' | xh POST example.org key=value
// after
echo '{"a":1}' | xh --ignore-stdin POST example.org key=value
Defensive patterns

Strategy: validation

Validate before calling

if !std::io::stdin().is_terminal() && !ignore_stdin && !request_items.is_body_empty() {
    eprintln!("piping a body and key=value items cannot be mixed; add --ignore-stdin");
    std::process::exit(2);
}

Try / catch

match run(args) {
    Err(e) if e.to_string().contains("cannot be mixed") => {
        eprintln!("tip: add --ignore-stdin or drop the key=value items");
    }
    other => other,
}

Prevention

When it happens

Trigger: use_stdin is true (stdin piped, --ignore-stdin not set), args.request_items.is_body_empty() is false, and args.multipart is false in run().

Common situations: Piping JSON with `echo data | xh POST url key=value`; shell scripts where stdin happens to be a pipe in CI; forgetting that a non-terminal stdin counts as a body.

Related errors


AI-assisted analysis of ducaale/xh@2404aceecc (2026-09-13). Data as JSON: /api/errors/cf4fa152751938d3. Report an issue: GitHub.

Appendix: source

Thrown at src/main.rs:122

    if args.curl {
        to_curl::print_curl_translation(args)?;
        return Ok(ExitCode::SUCCESS);
    }

    let (mut headers, headers_to_unset) = args.request_items.headers()?;
    let url = url_with_query(args.url, &args.request_items.query()?);
    log::debug!("Complete URL: {url}");

    let use_stdin = !(args.ignore_stdin || io::stdin().is_terminal() || test_pretend_term());

    let body = if use_stdin {
        if !args.request_items.is_body_empty() {
            if args.multipart {
                // Multipart bodies are never "empty", so we can get here without request items
                return Err(anyhow!("Cannot build a multipart request body from stdin"));
            } else {
                return Err(anyhow!(
                    "Request body (from stdin) and request data (key=value) cannot be mixed. \
                    Pass --ignore-stdin to ignore standard input."
                ));
            }
        }
        if args.raw.is_some() {
            return Err(anyhow!(
                "Request body from stdin and --raw cannot be mixed. \
                Pass --ignore-stdin to ignore standard input."
            ));
        }
        let mut buffer = Vec::new();
        io::stdin().read_to_end(&mut buffer)?;
        Body::Raw(buffer)
    } else if let Some(raw) = args.raw {
        Body::Raw(raw.into_bytes())
    } else {
        args.request_items.body()?

View on GitHub (pinned to 2404aceecc)