AprilNEA/OpenLogi · error

resumable fixture cases contain a non-UTF-8 entry

Error message

resumable fixture cases contain a non-UTF-8 entry

What it means

During a resumed `openlogi fixture contribute` run, validate_resumable_cases scans the saved `cases/` directory of the in-progress contribution and requires every directory entry name to be valid UTF-8 before comparing it against the expected cassette filenames. If any entry's file name cannot be converted to a Rust `str` (OsStr not valid UTF-8), the CLI refuses to proceed. This guards the publish path from writing over a corrupted or hand-mangled resumable state directory.

Solutions

  1. List the cases directory (`ls -b <output>/cases/`) and find the entry whose name prints with escape sequences
  2. Delete or rename the non-UTF-8 entry to a name matching an expected cassette `<name>.json`, or remove it if it is stray
  3. If the directory is badly mangled, delete the resumable state file and restart the contribution from scratch
  4. Only create/edit files in the fixture output directory using UTF-8-safe tools

Example fix

// before (shell): stray file with raw-byte name in the resumable output
$ ls output/cases/
my-cassette.json  '\xff\xfe.tmp'
// after: remove or rename the invalid entry, then rerun
$ rm output/cases/'\xff\xfe.tmp'
$ openlogi fixture contribute ...  # resumes cleanly
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;
use std::ffi::OsStr;

fn cases_have_utf8_names(cases_dir: &std::path::Path) -> std::io::Result<bool> {
    for entry in fs::read_dir(cases_dir)? {
        if entry?.file_name().to_str().is_none() {
            return Ok(false);
        }
    }
    Ok(true)
}

Type guard

fn is_utf8_name(entry: &std::fs::DirEntry) -> bool {
    entry.file_name().to_str().is_some()
}

Prevention

When it happens

Trigger: Resuming a contribution (`openlogi fixture contribute` with an existing state file) while the `<output>/cases/` directory contains any file or subdirectory whose name is not valid UTF-8 — e.g. a file created with raw bytes from a Linux tool, a name with invalid-encoded characters, or a corrupted entry produced by a filesystem crash.

Common situations: Developers on Linux who dropped temporary files with arbitrary byte names into the cases directory; rsync/backup restores that mangled non-ASCII cassette names; manually editing or cleaning the in-progress fixture output directory mid-contribution; a disk-level corruption leaving a garbage-named entry.

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/1456652abcaeb7fc. Report an issue: GitHub.

Appendix: source

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

                "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:?}");
        }
        require_regular_file(&entry.path(), "fixture cassette")?;
    }
    Ok(())
}

fn identity_plan(
    profile: &DeviceProfile,
    selected_route: &DeviceRoute,
) -> Result<HidCassetteIdentityPlan> {
    let mut plan = HidCassetteIdentityPlan::default();
    let model = match selected_route {
        DeviceRoute::Bolt { receiver_uid, slot } => {
            plan.insert(
                SanitizedIdentityKind::ReceiverUniqueId,

View on GitHub (pinned to e846e6f4b4)