AprilNEA/OpenLogi · error

in-progress contribution contains a non-UTF-8 entry

Error message

in-progress contribution contains a non-UTF-8 entry

What it means

During the same pre-publish layout audit of the contribution directory, any directory entry whose filename is not valid UTF-8 cannot be matched against the expected entry names (state, profile, manifest, cases). Since the tool cannot classify it, it bails with this message rather than publishing over or silently deleting unknown content. Filenames here are expected to be UTF-8 (they become part of committed fixture data).

Solutions

  1. Find the offending entry (`find DIR -name '*\?*'` or `ls -b DIR`) and delete or rename it to valid UTF-8, then rerun.
  2. Recreate the contribution directory with clean UTF-8 content: move expected files out, remove the directory, and rerun the contribute workflow.
  3. Avoid copying contribution directories through non-UTF-8-preserving tools or legacy network shares.
  4. If this recurs without explanation, check the filesystem for corruption (`fsck` on Linux, Disk Utility first aid on macOS).

Example fix

# before
ls -b fx/  ->  profile.json  state.json  fx\xff\xfe/
# error: non-UTF-8 entry

# after
rm -rf 'fx/'fx$'\xff\xfe'    # or rename it to a UTF-8 name
openlogi fixture contribute --output fx ...
Defensive patterns

Strategy: validation

Validate before calling

fn has_non_utf8_names(dir: &Path) -> anyhow::Result<bool> {
    Ok(fs::read_dir(dir)?
        .filter_map(Result::ok)
        .any(|e| e.file_name().to_str().is_none()))
}
// if true, find and rename/remove the offending entry before publishing

Prevention

When it happens

Trigger: Running the publish/finish phase of `openlogi fixture contribute` when the contribution directory contains an entry with non-UTF-8 bytes in its name — typically created by a tool on a platform/filesystem with different encoding conventions, or by a filesystem-corruption event.

Common situations: Files created with legacy encodings (e.g. Latin-1 names via Samba/NFS mounts or old archive extraction) inside DIR; mangled names after a failed copy; filesystem corruption; copying the contribution directory through a tool that re-encoded names.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13). Data as JSON: /api/errors/8d04e20e45ba3b5f. Report an issue: GitHub.

Appendix: source

Thrown at crates/openlogi-cli/src/cmd/fixture/contribute.rs:295

fn validate_resumable_layout(directory: &Path, cassettes: &[HidCassette]) -> Result<()> {
    for entry in
        fs::read_dir(directory).context("could not inspect resumable contribution directory")?
    {
        let entry = entry.context("could not inspect resumable contribution entry")?;
        let name = entry.file_name();
        match name.to_str() {
            Some(STATE_FILE | PROFILE_FILE) => {}
            Some(MANIFEST_FILE) => require_regular_file(&entry.path(), "fixture manifest")?,
            Some(CASES_DIRECTORY) if !cassettes.is_empty() => {
                require_directory(&entry.path(), "fixture cases directory")?;
                validate_resumable_cases(&entry.path(), cassettes)?;
            }
            Some(other) => bail!(
                "in-progress contribution contains unexpected entry {other:?}; refusing to \
                 publish over it"
            ),
            None => bail!("in-progress contribution contains a non-UTF-8 entry"),
        }
    }
    Ok(())
}

fn validate_resumable_cases(directory: &Path, cassettes: &[HidCassette]) -> Result<()> {
    let expected = cassettes
        .iter()
        .map(|cassette| format!("{}.json", cassette.name))
        .collect::<Vec<_>>();
    for entry in fs::read_dir(directory).context("could not inspect resumable fixture cases")? {
        let entry = entry.context("could not inspect resumable fixture case")?;
        let name = entry.file_name();
        let Some(name) = name.to_str() else {
            bail!("resumable fixture cases contain a non-UTF-8 entry");
        };
        if !expected.iter().any(|expected| expected == name) {
            bail!("resumable fixture cases contain unexpected file {name:?}");

View on GitHub (pinned to e846e6f4b4)