ducaale/xh · error

Invalid UTF-8

Error message

Invalid UTF-8

What it means

The curl translation layer builds a command line whose -o/--output argument must be a valid UTF-8 string. Path arguments on Unix can be arbitrary bytes; when args.output is not valid UTF-8, translate returns this error instead of emitting a malformed curl command.

Solutions

  1. Rename the output file to valid UTF-8 before translating
  2. Run with a UTF-8 locale (e.g. LANG=C.UTF-8) so paths decode correctly
  3. Fix the source of the filename (e.g. ls/find output) to produce valid UTF-8
  4. Skip curl translation for that invocation and download directly with xh

Example fix

// before
xh --curl -o "$RAW_BYTES_NAME" https://example.org
// after
export LANG=C.UTF-8
xh --curl -o output.bin https://example.org
Defensive patterns

Strategy: validation

Validate before calling

if !std::str::from_utf8(output_path.as_os_str().as_encoded_bytes()).is_ok() {
    eprintln!("output filename must be valid UTF-8 for --curl translation");
    std::process::exit(2);
}

Type guard

fn is_utf8_path(p: &std::path::Path) -> bool {
    p.to_str().is_some()
}

Try / catch

match result {
    Err(e) if e.to_string() == "Invalid UTF-8" => {
        eprintln!("rename output file to valid UTF-8");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Passing -o/--output a filename containing invalid UTF-8 bytes (common with OS-supplied names from a non-UTF-8 locale or corrupted filenames), then running `xh --curl`/print_curl_translation.

Common situations: Linux systems with filenames that are raw bytes from another encoding (Latin-1, SJIS); shell variables expanded from file listings with mojibake; scripts on systems without UTF-8 locale.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at src/to_curl.rs:176

        cmd.opt("-N", "--no-buffer");
    }
    // Since --fail is more disruptive than HTTPie's --check-status flag, we will not enable
    // it unless the user explicitly sets the latter flag
    if args.check_status == Some(true) {
        // Suppresses output on failure, unlike us
        cmd.opt("-f", "--fail");
    }

    // HTTP options
    if args.follow {
        cmd.opt("-L", "--location");
    }
    if let Some(num) = args.max_redirects {
        cmd.arg("--max-redirs");
        cmd.arg(num.to_string());
    }
    if let Some(filename) = args.output {
        let filename = filename.to_str().ok_or_else(|| anyhow!("Invalid UTF-8"))?;
        cmd.opt("-o", "--output");
        cmd.arg(filename);
    } else if args.download {
        cmd.opt("-O", "--remote-name");
    }
    if args.resume {
        cmd.opt("-C", "--continue-at");
        cmd.arg("-"); // Tell curl to guess, like we do
    }
    match args.verify.unwrap_or(Verify::Yes) {
        Verify::CustomCaBundle(filename) => {
            cmd.arg("--cacert");
            cmd.arg(filename);
        }
        Verify::No => {
            cmd.opt("-k", "--insecure");
        }
        Verify::Yes => {}

View on GitHub (pinned to 2404aceecc)