AprilNEA/OpenLogi · warning · io::Error
host device I/O is suspended
Error message
host device I/O is suspended
What it means
`openlogi-hid`'s transport exposes a `DeviceIoGate` so the host can suspend HID writes (e.g. while the agent is not the foreground owner or during permission/teardown states). When a `write_report` arrives through `device_io_error`/`device_io_suspended` while the gate is closed, the backend reports `BackendError::Backend("host device I/O is suspended")`. It is a deliberate backpressure signal, not a device malfunction.
Solutions
- Wait for the suspend to lift and retry the write — this is `WouldBlock` semantics, a retry is the correct response.
- Subscribe to `DeviceIoSignal` and queue writes until the gate reopens instead of issuing them while suspended.
- Check whether another component deliberately suspended I/O (pairing, exclusive capture, permission flow) and coordinate with it.
- If suspensions are unexpected, verify agent state transitions — a stuck gate indicates an owner never resumed I/O.
Example fix
// before — fire-and-forget write that fails while suspended channel.write_report(&report).await?; // after — wait for I/O to resume, then write device_io_channel().resumed().await; // or poll the gate state channel.write_report(&report).await?;
Defensive patterns
Strategy: retry
Validate before calling
// check the gate before issuing writes
if device_io_gate().is_suspended() {
// queue the write; do not call write_report yet
} Try / catch
match channel.write_report(&report).await {
Err(err) if err.to_string().contains("host device I/O is suspended") => {
// WouldBlock semantics: wait for the DeviceIoSignal resume, then retry
}
other => other?,
} Prevention
- Subscribe to DeviceIoSignal and only write while the gate is open.
- Queue HID++ writes during suspension windows instead of failing.
- Verify no component leaves the gate suspended after pairing/permission flows.
When it happens
Trigger: Calling `write_report` (via the HID++ channel) while the `DeviceIoGate` is suspended — the gate's error constructor builds an `io::ErrorKind::WouldBlock` error carrying this message, surfaced as a `BackendError::Backend`.
Common situations: Agent performing DPI/config writes during a suspension window (e.g. permission revocation, host pause, another process owning the device, or an internal pause like pairing/capture); a queued command from the GUI racing a suspend/resume transition.
Related errors
- raw-HID devices support profile-only contributions
- the selected device has no stable synthetic identity for…
- the selected direct-capture target does not match the…
- failed to enumerate HID++ devices for direct fixture capture
- unsupported light product
AI-assisted analysis of AprilNEA/OpenLogi@e846e6f4b4 (2026-09-13).
Data as JSON: /api/errors/e88c61f182aee99f.
Report an issue: GitHub.
Appendix: source
Thrown at crates/openlogi-hid/src/transport.rs:56
/// now that the contract lives in `openlogi-device`, which is the orphan rule
/// saying out loud what the layering already did — an adapter belongs to the
/// backend it adapts. `Disconnected` and `NotConnected` fold together; nothing
/// above the transport acts on the distinction.
fn backend_error(error: async_hid::HidError) -> BackendError {
match error {
async_hid::HidError::Disconnected | async_hid::HidError::NotConnected => {
BackendError::Disconnected
}
other => BackendError::Backend(other.to_string()),
}
}
fn device_io_suspended() -> BackendError {
BackendError::Backend("host device I/O is suspended".into())
}
fn device_io_error() -> Box<dyn Error + Send + Sync> {
std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"host device I/O is suspended",
)
.into()
}
/// Classify a failed device open. On macOS `IOHIDDeviceOpen` denies silently —
/// the error is indistinguishable from exclusive access — so fold the Input
/// Monitoring state into the message: it is the difference between "grant the
/// permission" and "close the other app, or log out and back in".
#[cfg(not(target_os = "windows"))]
fn open_error(error: async_hid::HidError) -> BackendError {
match backend_error(error) {
#[cfg(target_os = "macos")]
BackendError::Backend(message) => {
let hint = if crate::permissions::has_access() {
"Input Monitoring is granted to this process — another app may \
hold the device exclusively, or macOS is serving a stale \View on GitHub (pinned to e846e6f4b4)