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
- 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.
- Verify the exact `--id` and `--name` values used in the first run and repeat them verbatim in the resume run.
- If you hand-edited the profile, revert those edits so `id` and `name` match the values in the state file again.
- 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
- Repeat the exact `--id` and `--name` values in both contribute runs.
- Never hand-edit profile.json between step 1 and step 2.
- Keep the contribution directory untouched between the two runs; recapture the profile if you must change identity.
- Start from a fresh directory whenever you want a different id/name.
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
- --id and --name must match the in-progress contribution
- already exists but is not an in-progress OpenLogi…
- in-progress contribution contains unexpected entry
- in-progress contribution contains a non-UTF-8 entry
- --id must be a nonempty synthetic path component
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)