ducaale/xh · error

Request body from stdin and --raw cannot be mixed. Pass…

Error message

Request body from stdin and --raw cannot be mixed. Pass --ignore-stdin to ignore standard input.

What it means

The CLI found both piped stdin content and the --raw flag, which are two competing sources for the raw request body. It refuses to guess and asks the user to pick one, using --ignore-stdin to silence the piped body.

Solutions

  1. Pass --ignore-stdin so only the --raw body is sent.
  2. Remove --raw and send the body via stdin instead.
  3. Feed the raw content through stdin only (e.g. heredoc) and drop the --raw argument.
  4. Redirect stdin from /dev/null when --raw is used in scripts.

Example fix

// before
cat body.json | xh POST example.org --raw '{"a":1}'
// after
xh POST example.org --raw '{"a":1}' < /dev/null
Defensive patterns

Strategy: validation

Validate before calling

if !std::io::stdin().is_terminal() && !ignore_stdin && args.raw.is_some() {
    eprintln!("--raw conflicts with piped stdin; add --ignore-stdin or < /dev/null");
    std::process::exit(2);
}

Try / catch

match run(args) {
    Err(e) if e.to_string().contains("--raw cannot be mixed") => {
        eprintln!("tip: keep --raw and add --ignore-stdin");
    }
    other => other,
}

Prevention

When it happens

Trigger: use_stdin is true (stdin piped, --ignore-stdin not set) and args.raw.is_some() in run(), after the request-items check passes.

Common situations: Running `echo x | xh POST url --raw '{...}'` in scripts; CI environments where stdin is a pipe even when unused; combining raw body flags with heredoc/stdin patterns out of habit.

Related errors


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

Appendix: source

Thrown at src/main.rs:129

    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()?
    };

    let method = args.method.unwrap_or_else(|| body.pick_method());
    log::debug!("HTTP method: {method}");

    let mut client = Client::builder()
        .http1_title_case_headers()

View on GitHub (pinned to 2404aceecc)