Skip to content

fix(device): don't abort the ioreg scan on one unparseable id - #97

Draft
DevVig wants to merge 1 commit into
mainfrom
fix/ioreg-probe-scan
Draft

fix(device): don't abort the ioreg scan on one unparseable id#97
DevVig wants to merge 1 commit into
mainfrom
fix/ioreg-probe-scan

Conversation

@DevVig

@DevVig DevVig commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Why

system_profiler SPUSBDataType returns zero bytes with exit 0 on some macOS
builds, so USB presence detection silently found nothing. Reproduced on macOS 27,
where ioreg is the only path that works at all.

This adds the ioreg -p IOUSB -l fallback, and fixes three bugs in the parser.

The three parser bugs

Bug Consequence
? on a failed parse::<u16>() inside the loop One device with an unparseable idProduct returned None for the whole scan, hiding every device listed after it — including the Micro
current_vid never cleared One device's vendor id could pair with the next device's product id
Assumed idVendor precedes idProduct ioreg's property dictionary does not promise that order

Parsing is now scoped per +-o device node, mirroring how match_usb_text
already scopes to a record block — the two ids only mean anything together. Also
tolerates a 0x form, so a hex listing isn't read as "nothing attached".

How this was tested

  • cargo test -p mb-device — 20 tests, 4 new, one per failure mode:
    malformed-id abort, cross-device id pairing, reversed property order, hex ids.
  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings clean.
  • Confirmed the premise on this machine: system_profiler SPUSBDataType → 0 bytes,
    exit 0. The IOUSB plane is healthy (three XHCI controllers listed).

Not verified

No hardware. With no Codex Micro attached the IOUSB plane lists no devices, so
the parser has not been run against a real listing. The unit tests use captured
ioreg shapes. Worth a manual check with the device plugged in before relying on
detection.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved macOS USB device detection by checking multiple system sources.
    • Added support for decimal and hexadecimal device identifiers.
    • Improved handling of malformed device data without overlooking valid devices.
    • Added name-based detection for supported devices when identifier data is unavailable.

Adds an `ioreg -p IOUSB -l` fallback for USB presence, because
`system_profiler SPUSBDataType` returns zero bytes with exit 0 on some
macOS builds — reproduced on macOS 27, where it is the only working path.

The first cut of the ioreg parser had three problems, all fixed here:

- `?` on a failed `parse::<u16>()` inside the loop returned `None` for the
  whole scan, so one device with an unparseable `idProduct` hid every device
  listed after it — including the Micro.
- `current_vid` was never cleared, so one device's vendor id could pair with
  the next device's product id.
- It assumed `idVendor` precedes `idProduct`, an ordering ioreg's property
  dictionary does not promise.

Parsing is now scoped per `+-o` device node, mirroring how `match_usb_text`
already scopes to a record block, since the two ids only mean anything
together. Also tolerates a `0x` form rather than reading a hex listing as
"nothing attached".

Tested: `cargo test -p mb-device` (20 tests, 4 new covering the malformed-id
abort, cross-device pairing, reversed property order, and hex ids). Not yet
exercised against attached hardware — no Codex Micro available, and the
IOUSB plane lists no devices without one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 13:18
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

macOS USB probe

Layer / File(s) Summary
ioreg matching and validation
crates/mb-device/src/probe.rs
Adds per-node ioreg parsing for decimal and hexadecimal IDs, name fallback, malformed-input handling, vendor/product pairing, and focused tests.
Probe fallback and command execution
crates/mb-device/src/probe.rs
Falls back from system_profiler to ioreg and invokes both macOS utilities through absolute paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProbeUsbMicro
  participant SystemProfiler
  participant Ioreg
  participant ProbeResult
  ProbeUsbMicro->>SystemProfiler: request USB text
  SystemProfiler-->>ProbeUsbMicro: return output
  ProbeUsbMicro->>Ioreg: request USB text when unmatched
  Ioreg-->>ProbeUsbMicro: return output
  ProbeUsbMicro->>ProbeResult: return present or absent
Loading

Possibly related PRs

Suggested reviewers: copilot

Poem

I hop through profiler, then ioreg’s light,
Pairing IDs neatly, malformed or right.
Codex Micro appears in the stream,
Work Louder follows like a bright little dream.
USB is found with a bounce and a cheer—
“Present!” twitches whiskers: the device is here!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches a real part of the change, though it only highlights one parser fix and not the broader macOS ioreg fallback work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves macOS USB presence detection for the Codex Micro by adding an ioreg -p IOUSB -l fallback when system_profiler SPUSBDataType returns empty output, and by tightening ioreg parsing to avoid cross-device VID/PID pairing and to tolerate malformed/hex IDs.

Changes:

  • Add ioreg-based probe fallback when system_profiler is empty or fails to detect the device.
  • Implement node-scoped ioreg parsing for VID/PID matching (with hex tolerance) plus name-based fallback.
  • Add unit tests covering multiple ioreg parsing edge cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +112 to +116
let line = line.trim();
// A malformed value must skip that property, never abort the scan: the
// Codex Micro may be listed after whatever failed to parse.
if let Some(rest) = line.strip_prefix("\"idvendor\" =") {
vid = vid.or_else(|| parse_ioreg_id(rest));

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/mb-device/src/probe.rs`:
- Around line 164-178: Update ioreg_usb_text to invoke ioreg with the same
bounded-process mechanism used by system_profiler_usb_text, preserving its
existing arguments and success/output handling while ensuring stalled processes
terminate instead of blocking the USB probe indefinitely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47ba3163-821d-4767-869c-bba626a8c9ef

📥 Commits

Reviewing files that changed from the base of the PR and between fcd0aba and d6f793a.

📒 Files selected for processing (1)
  • crates/mb-device/src/probe.rs

Comment on lines +164 to +178
#[cfg(target_os = "macos")]
fn ioreg_usb_text() -> Option<String> {
use std::process::{Command, Stdio};

let output = Command::new("/usr/sbin/ioreg")
.args(["-p", "IOUSB", "-l"])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()?;
if !output.status.success() {
return None;
}
Some(String::from_utf8_lossy(&output.stdout).into_owned())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound the ioreg invocation.

Command::output() waits indefinitely. When system_profiler fails/returns empty, a stalled ioreg now blocks the USB presence probe forever. Apply the same bounded-process behavior used for system_profiler_usb_text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/mb-device/src/probe.rs` around lines 164 - 178, Update ioreg_usb_text
to invoke ioreg with the same bounded-process mechanism used by
system_profiler_usb_text, preserving its existing arguments and success/output
handling while ensuring stalled processes terminate instead of blocking the USB
probe indefinitely.

@DevVig DevVig closed this Jul 25, 2026
@DevVig DevVig reopened this Jul 25, 2026
@DevVig
DevVig marked this pull request as draft July 25, 2026 13:42
@DevVig

DevVig commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

Hold — this PR's premise is superseded by v0.3.9

I opened this against a stale local main (6 commits behind, spanning
v0.3.8 → v0.3.10). On current main, probe.rs was rewritten by
e642bf5 fix: detect and control Codex Micro over HID (v0.3.9):

  • probe_hid_micro() enumerates through hidapi directly and is
    authoritative — Ok(None) returns absent without consulting any CLI tool.
  • system_profiler is now only reached when built without the hid feature
    (which is default = ["hid"]) or if HidApi::new() fails.
  • ProbeResult gained a transport field this PR knows nothing about.

So the bug this PR fixes — system_profiler SPUSBDataType returning zero bytes
with exit 0, reproduced here on macOS 27 — no longer bites on the normal
path
, because hidapi depends on neither system_profiler nor ioreg. That is
a better fix than the ioreg fallback proposed here.

An ioreg fallback would now only help a --no-default-features build whose
system_profiler also returns nothing. Marginal, and the diff would need a full
rewrite against the new probe_usb_micro shape and ProbeResult.

Recommendation: close. The parser-correctness work here (per-+-o-node id
scoping; not aborting the whole scan on one unparseable value) has no equivalent
on main, because main no longer parses ioreg at all.

One thing worth salvaging separately

main still shells out PATH-relative:

let mut child = Command::new("system_profiler")   // probe.rs:207

This PR hardened that to /usr/sbin/system_profiler. Worthwhile independently of
the ioreg question, and a one-line change.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants