astral-sh/ruff · error · io::Error

Invalid UTF-8 in JUnit report

Error message

Invalid UTF-8 in JUnit report

What it means

This io::Error (kind InvalidData) is produced by ruff's JUnit report `FmtAdapter`, a `std::io::Write` adapter that forwards bytes to `fmt::Write`. Because `fmt::Write` only accepts strings, the adapter validates each incoming buffer as UTF-8 and fails with this message if it is not.

Source

Thrown at crates/ruff_db/src/diagnostic/render/junit.rs:177

            .entry(filename)
            .or_insert_with(Vec::new)
            .push(DiagnosticWithLocation {
                diagnostic,
                start_location,
            });
    }
    grouped_diagnostics
}

struct FmtAdapter<'a> {
    fmt: &'a mut dyn std::fmt::Write,
}

impl std::io::Write for FmtAdapter<'_> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.fmt
            .write_str(std::str::from_utf8(buf).map_err(|_| {
                std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Invalid UTF-8 in JUnit report",
                )
            })?)
            .map_err(std::io::Error::other)?;

        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }

    fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> {
        self.fmt.write_fmt(args).map_err(std::io::Error::other)
    }
}

View on GitHub (pinned to 26f38c119c)

Solutions

  1. Ensure all content written to the JUnit report is valid UTF-8 (decode with lossy conversion at the source)
  2. Check where non-UTF-8 bytes enter the report (often a source file read); normalize with `String::from_utf8_lossy`
  3. Route byte output through an explicit encoding step before the JUnit writer

Example fix

// before
adapter.write_all(raw_bytes)?; // may contain invalid UTF-8
// after
adapter.write_all(String::from_utf8_lossy(raw_bytes).as_bytes())?;
Defensive patterns

Strategy: try-catch

Validate before calling

if let Err(e) = std::str::from_utf8(buf) {
    eprintln!("non-UTF-8 content for JUnit report: {e}");
}
let safe = String::from_utf8_lossy(buf);

Type guard

fn is_valid_utf8(buf: &[u8]) -> bool {
    std::str::from_utf8(buf).is_ok()
}

Try / catch

match write_result {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {
        eprintln!("JUnit report content was not valid UTF-8");
    }
    Err(e) => return Err(e),
    Ok(n) => n,
}

Prevention

When it happens

Trigger: Writing bytes that are not valid UTF-8 into a JUnit-format report writer — e.g. `write!` with content derived from non-UTF-8 data, or downstream code that pushes raw byte slices into the adapter.

Common situations: Diagnostics containing text decoded from files with non-UTF-8 encodings, lossy conversions upstream, or mixing a byte-level writer API with the string-level JUnit formatter.

Understand the failure class

Related errors


AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05). Data as JSON: /api/errors/504040c6cceecc08. Report an issue: GitHub.