AprilNEA/OpenLogi · error

saved contribution profile does not match its resumable…

Error message

saved contribution profile does not match its resumable state

What it means

When resuming a two-step fixture contribution, the CLI compares the saved `DeviceProfile` JSON in the output directory against the IDs/names recorded in the resumable contribution state. This bail fires when `profile.id` or `profile.name` differ from `state.profile_id` / `state.profile_name`, meaning the profile file and state file no longer describe the same contribution. Publishing would produce a manifest whose identity contradicts the resumable state, so the command aborts.

Solutions

  1. Delete the stale profile file and rerun the same `openlogi fixture contribute` command to recapture the profile fresh in step 1, keeping `--id`/`--name` identical between runs.
  2. Verify the exact `--id` and `--name` values used in the first run and repeat them verbatim in the resume run.
  3. If you hand-edited the profile, revert those edits so `id` and `name` match the values in the state file again.
  4. As a last resort, start over with a fresh output directory.

Example fix

// before (resume with different identity)
openlogi fixture contribute --output ./fx --id new-id --name "Other Mouse"
// error: profile (old-id/"Old Mouse") != state (old-id/"Old Mouse" vs new args)

// after (reuse the exact step-1 identity, or recapture)
openlogi fixture contribute --output ./fx --id old-id --name "Old Mouse"
// or: remove profile.json + state.json and start step 1 again
Defensive patterns

Strategy: validation

Validate before calling

fn identities_match(state: &ContributionState, profile: &DeviceProfile) -> bool {
    profile.id == state.profile_id && profile.name == state.profile_name
}
// call before rerunning contribute; if false, recapture the profile in step 1

Prevention

When it happens

Trigger: Running `openlogi fixture contribute --output DIR ...` in the resume (step 2) phase after the profile file in DIR was regenerated, hand-edited, replaced with a different device's profile, or captured with a different `--id`/`--name` than the state file recorded.

Common situations: Manually editing the profile JSON (e.g. renaming the device) between runs; re-running step 1 with different `--id`/`--name` into a directory that still holds the old profile; copying a profile from another contribution directory; a partial step-1 run that wrote the profile but then re-wrote state with different values.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

        "The second step uses this CLI process's own HID permission and records eight read-only \
         operations. Discovery may first enable notifications and request arrival reports on \
         connected receivers; notification flags are not restored."
    );
    Ok(())
}

async fn finish(args: ContributeArgs, state_path: &Path) -> Result<()> {
    require_regular_file(state_path, "contribution state")?;
    let state: ContributionState = read_json(state_path, "contribution state")?;
    validate_state(&args, &state)?;
    let profile_path = args.output.join(PROFILE_FILE);
    require_regular_file(&profile_path, "captured device profile")?;
    let profile: DeviceProfile = read_json(&profile_path, "captured device profile")?;
    profile
        .validate()
        .context("saved contribution profile is invalid")?;
    if profile.id != state.profile_id || profile.name != state.profile_name {
        bail!("saved contribution profile does not match its resumable state");
    }

    if args.profile_only {
        publish_manifest_and_finish(&args.output, state_path, &profile, &[], &[])?;
        println!(
            "Finished a profile-only contribution at {}.",
            args.output.display()
        );
        return Ok(());
    }

    println!("Step 2/2: Agent must be stopped; selecting the same structural target…");
    let target = record_case::prepare_contribution_target(args.device.as_deref()).await?;
    require_same_structural_route(&state.selected_route, target.route())?;
    let identity_plan = identity_plan(&profile, &state.selected_route)?;

    let mut cassettes = Vec::with_capacity(FixtureOperation::ALL.len());
    for (index, operation) in FixtureOperation::ALL.into_iter().enumerate() {

View on GitHub (pinned to e846e6f4b4)