AprilNEA/OpenLogi · error

raw-HID devices support profile-only contributions

Error message

raw-HID devices support profile-only contributions

What it means

`identity_plan` builds the sanitized identity plan required for full cassette capture, but raw-HID routes carry no HID++ receiver/slot model information, so no per-device identity can be derived from them. The CLI therefore rejects full cassette capture for raw-HID devices and directs the user to a profile-only contribution, which does not need cassette identity relationships.

Solutions

  1. Rerun the contribution with `--profile-only`, which is the supported path for raw-HID devices
  2. Check `openlogi list` to see how the device is routed; if it should be a direct HID++ device, verify it is connected via a supported transport (direct USB/BT or Bolt/Unifying receiver)
  3. Select a different `--device` selector that resolves to a Direct/Bolt/Unifying route if the device offers multiple interfaces
  4. Update device-registry metadata/driver definitions if a legitimately HID++-capable device is being misclassified as raw-HID

Example fix

// before: full capture on a raw-HID route
$ openlogi fixture contribute --id my-fixture --device 046d:c52b --output output/my-fixture
Error: raw-HID devices support profile-only contributions
// after: use the supported contribution mode
$ openlogi fixture contribute --id my-fixture --device 046d:c52b --output output/my-fixture --profile-only
Defensive patterns

Strategy: validation

Validate before calling

// Check the device route before requesting full capture
let route = device.route();
if matches!(route, DeviceRoute::RawHid { .. }) {
    eprintln!("device is raw-HID: rerun with --profile-only");
    std::process::exit(2);
}

Type guard

fn supports_full_capture(route: &DeviceRoute) -> bool {
    !matches!(route, DeviceRoute::RawHid { .. })
}

Try / catch

match contribute_result {
    Err(e) if e.to_string().contains("profile-only contributions") => {
        eprintln!("raw-HID device: rerun with --profile-only");
    }
    Err(e) => return Err(e),
    Ok(v) => println!("{v:?}"),
}

Prevention

When it happens

Trigger: Running `openlogi fixture contribute` (full capture, not `--profile-only`) while the selected `--device` resolves to a `DeviceRoute::RawHid` route — i.e. the device was enumerated as a plain raw HID interface rather than via Bolt/Unifying receiver or a direct HID++ connection.

Common situations: Device attached through a generic/vendor-specific interface that only exposes raw HID; receiver-less devices that the registry doesn't recognize as HID++-capable; selecting the wrong interface of a multi-interface device with `--device`; driver/registry metadata missing so the device falls back to raw-HID classification.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

            let ordinal = classify_synthetic_profile_identity(
                SyntheticIdentityKind::UnifyingReceiverRoute,
                receiver_uid,
            )?;
            let serial =
                generate_synthetic_identity(SyntheticIdentityKind::UnifyingReceiverSerial, ordinal)
                    .as_bytes()
                    .ok_or_else(|| anyhow!("could not derive synthetic Unifying serial"))?
                    .to_vec();
            plan.insert(SanitizedIdentityKind::ReceiverSerialNumber, serial)?;
            selected_model(profile, selected_route, *slot)?
        }
        DeviceRoute::Direct { .. } => selected_model(
            profile,
            selected_route,
            openlogi_core::hid::DIRECT_DEVICE_INDEX,
        )?,
        DeviceRoute::RawHid { .. } => {
            bail!("raw-HID devices support profile-only contributions")
        }
    };
    add_model_identities(&mut plan, model)?;
    Ok(plan)
}

fn selected_model<'a>(
    profile: &'a DeviceProfile,
    route: &DeviceRoute,
    slot: u8,
) -> Result<&'a DeviceModelInfo> {
    profile
        .inventories
        .iter()
        .find(|inventory| {
            inventory.paired.iter().any(|device| {
                DeviceRoute::device_route_for(inventory, device.slot).as_ref() == Some(route)
            })

View on GitHub (pinned to e846e6f4b4)