AprilNEA/OpenLogi · error
fixture directory has no UTF-8 synthetic ID
Error message
fixture directory has no UTF-8 synthetic ID
What it means
Thrown by `publish_manifest_and_finish` during fixture contribution when the fixture directory's `file_name()` cannot be converted to a UTF-8 string (`OsStr::to_str` returns None). The directory's final path component doubles as the synthetic fixture ID in the manifest, so a non-UTF-8 name makes an exact, stable fixture ID impossible. This is a filesystem-encoding invariant on the contribution path.
Solutions
- Rename the fixture directory to a UTF-8 name (ASCII recommended) and re-run the contribute/finish step.
- Check the name with `printf '%s' "$dir" | xxd | tail` or `ls | LC_ALL=C grep -P '[\x80-\xff]'` to spot invalid bytes.
- Re-extract or re-create the fixture directory from the source, forcing UTF-8 filenames.
- If this comes from an automated pipeline, make it generate fixture IDs from `[A-Za-z0-9_-]` only.
Example fix
// before: non-UTF-8 directory name $ openlogi fixture contribute finish /tmp/fixtures/caf$é-latin1 // error: fixture directory has no UTF-8 synthetic ID // after: rename to ASCII UTF-8 $ mv /tmp/fixtures/caf$'\xe9'-latin1 /tmp/fixtures/cafe-latin1 $ openlogi fixture contribute finish /tmp/fixtures/cafe-latin1
Defensive patterns
Strategy: validation
Validate before calling
// validate the fixture directory name is UTF-8 before starting the contribute flow
fn validate_fixture_dir(path: &Path) -> Result<(), String> {
match path.file_name().and_then(|n| n.to_str()) {
Some(name) if name.is_ascii() => Ok(()),
_ => Err(format!("fixture dir {:?} must have a UTF-8 (ASCII-safe) name", path)),
}
} Type guard
fn has_utf8_id(path: &std::path::Path) -> bool {
path.file_name().map(|n| n.to_str().is_some()).unwrap_or(false)
} Try / catch
match finish(directory) {
Err(e) if msg_contains(&e, "no UTF-8 synthetic ID") => {
eprintln!("rename the fixture directory to a UTF-8 name and retry: {e:#}");
}
other => other?,
} Prevention
- Create fixture directories with ASCII names only (`[A-Za-z0-9_-]`).
- Extract fixture archives with explicit UTF-8 filename handling.
- Sanitize directory names in automation before invoking contribute.
- On Linux, remember paths are raw bytes — never let external tools name fixture dirs.
When it happens
Trigger: Running the fixture contribute/finish flow against a directory whose final path component contains invalid UTF-8 bytes (possible on Linux where paths are arbitrary bytes, e.g. Latin-1 filenames created by other tools).
Common situations: Contributing a fixture directory created or extracted by a tool that wrote non-UTF-8 filenames; a directory name with raw bytes from a zip/tar with a legacy encoding; renaming automation that mangled the name.
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
- resumable fixture cases contain a non-UTF-8 entry
- in-progress contribution contains a non-UTF-8 entry
- resumable fixture cases contain unexpected file
- already exists but is not an in-progress OpenLogi…
- in-progress contribution contains unexpected entry
AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13).
Data as JSON: /api/errors/b0a30997cdffcf38.
Report an issue: GitHub.
Appendix: source
Thrown at crates/openlogi-cli/src/cmd/fixture/contribute.rs:244
args.output.display()
);
println!(
"No data was uploaded; physical and semantic correctness still require maintainer review."
);
Ok(())
}
fn publish_manifest_and_finish(
directory: &Path,
state_path: &Path,
profile: &DeviceProfile,
cassettes: &[HidCassette],
bindings: &[FixtureCaseBinding],
) -> Result<()> {
let fixture_id = directory
.file_name()
.and_then(OsStr::to_str)
.ok_or_else(|| anyhow!("fixture directory has no UTF-8 synthetic ID"))?;
let manifest =
FixtureManifest::from_assets(fixture_id.to_string(), profile, cassettes, bindings)
.context("could not generate an exact fixture manifest from sanitized assets")?;
validate_resumable_layout(directory, cassettes)?;
if !cassettes.is_empty() {
let cases_directory = directory.join(CASES_DIRECTORY);
if cases_directory
.try_exists()
.context("could not inspect cases directory")?
{
require_directory(&cases_directory, "fixture cases directory")?;
}
for cassette in cassettes {
let path = cases_directory.join(format!("{}.json", cassette.name));
reject_symlink(&path, "fixture cassette")?;
super::output::write_json_atomically(&path, cassette, true, "HID cassette")?;
}View on GitHub (pinned to e846e6f4b4)