rust-lang/cargo · error

Character at line is invalid. Cargo only supports UTF-8.

Error message

Character at line {} is invalid. Cargo only supports UTF-8.

What it means

While formatting/updating an existing VCS ignore file (`.gitignore`, `.hgignore`, etc.), cargo reads it line-by-line as UTF-8. If `BufRead::lines` returns `InvalidData` (the std `Utf8Error`-mapping for non-UTF-8 bytes), cargo surfaces it naming the offending line number. Cargo only handles UTF-8 content and refuses to round-trip binary/non-UTF-8 ignore files.

Solutions

  1. Re-save the ignore file as UTF-8 (`iconv -f LATIN1 -t UTF-8 .gitignore` or open and re-save in an editor set to UTF-8).
  2. Remove the offending non-UTF-8 bytes/lines (the error names the line number).
  3. Delete the ignore file and let cargo regenerate it, if its existing content is not worth keeping.

Example fix

# before — .gitignore contains non-UTF-8 bytes at line 12
$ cargo init   # error: Character at line 12 is invalid. Cargo only supports UTF-8.

# after
$ iconv -f LATIN1 -t UTF-8 .gitignore -o .gitignore.utf8 && mv .gitignore.utf8 .gitignore
$ cargo init
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;
use std::str;
fn ignore_file_is_utf8(path: &std::path::Path) -> bool {
    match fs::read(path) {
        Ok(bytes) => str::from_utf8(&bytes).is_ok(),
        Err(_) => false,
    }
}
// before `cargo init`, re-encode the ignore file to UTF-8 if this returns false.

Type guard

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

Prevention

When it happens

Trigger: `cargo init` (or `cargo new` with VCS) in a directory whose existing `.gitignore`/`.hgignore`/`.fossil-ignore` contains non-UTF-8 bytes (e.g. a stray binary blob, latin-1 encoded content, or a corrupted file). The error fires in `format_existing` during the ignore-list merge.

Common situations: A pre-existing `.gitignore` written by a tool that emitted non-UTF-8 (e.g. Windows-1252 on some legacy systems). Corruption from a bad merge or file transfer. A `.gitignore` that accidentally swallowed binary content.

Related errors


AI-assisted analysis of rust-lang/cargo@98a09e7e7d (2026-08-11). Data as JSON: /api/errors/298f41657629dfcf. Report an issue: GitHub.

Appendix: source

Thrown at src/ops/cargo_new.rs:609

            VersionControl::Fossil => &self.fossil_ignore,
            _ => &self.ignore,
        };

        ignore_items.join("\n") + "\n"
    }

    /// `format_existing` is used to format the `IgnoreList` when the ignore file
    /// already exists. It reads the contents of the given `BufRead` and
    /// checks if the contents of the ignore list are already existing in the
    /// file.
    fn format_existing<T: BufRead>(&self, existing: T, vcs: VersionControl) -> CargoResult<String> {
        let mut existing_items = Vec::new();
        for (i, item) in existing.lines().enumerate() {
            match item {
                Ok(s) => existing_items.push(s),
                Err(err) => match err.kind() {
                    ErrorKind::InvalidData => {
                        return Err(anyhow!(
                            "Character at line {} is invalid. Cargo only supports UTF-8.",
                            i
                        ));
                    }
                    _ => return Err(anyhow!(err)),
                },
            }
        }

        let ignore_items = match vcs {
            VersionControl::Hg => &self.hg_ignore,
            VersionControl::Fossil => &self.fossil_ignore,
            _ => &self.ignore,
        };

        let mut out = String::new();

        // Fossil does not support `#` comments.

View on GitHub (pinned to 98a09e7e7d)