ducaale/xh · error

Cannot build a multipart request body from stdin

Error message

Cannot build a multipart request body from stdin

What it means

When stdin is piped into the CLI while --multipart is set, the body would have to come from stdin, but a multipart body cannot be constructed from raw stdin bytes together with the multipart form framing. The CLI refuses rather than producing a broken request.

Solutions

  1. Pass the file as a request item (e.g. file_field@path) instead of piping it via stdin.
  2. Add --ignore-stdin so the CLI ignores the piped stdin and builds the multipart body from request items only.
  3. Redirect stdin from the terminal or run in an interactive shell.
  4. Drop --multipart if you actually want a raw stdin body.

Example fix

// before
cat data.bin | xh --multipart POST example.org
// after
xh --multipart POST example.org file@data.bin
Defensive patterns

Strategy: validation

Validate before calling

if !std::io::stdin().is_terminal() && !ignore_stdin && multipart_enabled {
    eprintln!("stdin is piped; pass --ignore-stdin or use field@file for multipart");
    std::process::exit(2);
}

Try / catch

match run(args) {
    Err(e) if e.to_string().contains("multipart request body from stdin") => {
        eprintln!("tip: use --ignore-stdin or pass files as request items");
    }
    other => other,
}

Prevention

When it happens

Trigger: run() detects use_stdin (stdin piped and not --ignore-stdin), args.request_items.is_body_empty() is false or multipart forces entry, and args.multipart is true; then this error is returned before building the request.

Common situations: Piping a file into the command while also passing --multipart; scripting usage like `cat file | xh --multipart POST url`; forgetting --ignore-stdin in CI where stdin is a pipe.

Related errors


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

Appendix: source

Thrown at src/main.rs:120

        return Ok(ExitCode::SUCCESS);
    }

    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())

View on GitHub (pinned to 2404aceecc)