AprilNEA/OpenLogi · error

resumable fixture cases contain unexpected file

Error message

resumable fixture cases contain unexpected file {name:?}

What it means

While validating a resumed contribution, every file found in the `cases/` directory must exactly match one of the cassette filenames (`<cassette-name>.json`) recorded in the resumable state. Any extra or differently-named entry causes this bail, preventing the CLI from publishing a fixture whose cassette set doesn't match the recorded state.

Solutions

  1. Inspect `<output>/cases/` and remove any file that isn't a cassette expected by the current contribution (backups, `.DS_Store`, stale JSON)
  2. If the extra file belongs to the fixture, delete the resumable state file and restart the contribution so the state is regenerated consistently
  3. Never manually add/rename cassette JSON files inside an in-progress contribution directory
  4. Use a fresh output directory per contribution id

Example fix

// before: resumable cases dir polluted by an editor backup
$ ls output/cases/
my-cassette.json  my-cassette.json~
// after: remove the unexpected file and rerun
$ rm output/cases/my-cassette.json~
$ openlogi fixture contribute ...  # validation passes
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;

fn cases_match_expected(cases_dir: &std::path::Path, expected: &[String]) -> std::io::Result<bool> {
    let mut actual: Vec<String> = fs::read_dir(cases_dir)?
        .filter_map(|e| e.ok())
        .filter_map(|e| e.file_name().to_str().map(str::to_string))
        .collect();
    actual.sort();
    let mut sorted = expected.to_vec();
    sorted.sort();
    Ok(actual == sorted)
}

Type guard

fn is_expected_cassette(name: &str, expected: &[String]) -> bool {
    expected.iter().any(|e| e == name)
}

Prevention

When it happens

Trigger: Resuming `openlogi fixture contribute` when `<output>/cases/` contains a file not listed in the resumable state — e.g. leftover cassette JSON from a previous attempt with a different fixture id, editor backup files (`foo.json~`, `.DS_Store`), or a manually renamed/added cassette file.

Common situations: Reusing an output directory across contribution attempts with different `--id`; editors or OS tools leaving backup/metadata files in the cases directory; hand-renaming a cassette JSON to fix a typo; interrupted earlier run leaving stale cassettes.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

            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,
                receiver_uid.as_bytes().to_vec(),
            )?;
            selected_model(profile, selected_route, *slot)?

View on GitHub (pinned to e846e6f4b4)