Summary
FBSimulatorIndigoHIDTransport.indigo(for:) evaluates its initializer arguments in declaration order:
static func indigo(for simulator: FBSimulator) throws -> FBSimulatorIndigoHIDTransport {
FBSimulatorIndigoHIDTransport(
indigoClient: try FBSimulatorIndigoHIDClient(for: simulator.device), // 1st: objc_lookUpClass("SimulatorKit.SimDeviceLegacyHIDClient")
indigo: try FBSimulatorIndigoHID(), // 2nd: loads SimulatorKit via xcodeFrameworks loader
...
FBSimulatorIndigoHIDClient.init(for:) looks up SimulatorKit.SimDeviceLegacyHIDClient with objc_lookUpClass, but the thing that actually loads SimulatorKit into the process — FBSimulatorControlFrameworkLoader.xcodeFrameworks.loadPrivateFrameworks() inside FBSimulatorIndigoHID.init() — runs after it. Any process that has not already loaded SimulatorKit by other means throws FBSimulatorHIDError.clientClassUnavailable on the entire Indigo path.
This includes the default transport selection: on a simulator where dtuhidd is not active, FBSimulator.defaultHIDTransport resolves to .indigo, so a plain FBSimulatorHID(for: simulator) fails.
Why idb_companion does not hit this
FBIDBCommandExecutor.connectToHID() calls FBSimulatorControlFrameworkLoader.xcodeFrameworks.loadPrivateFrameworks(...) before connectToHID, so inside the companion SimulatorKit is always resident before the client lookup runs. The bug only surfaces for consumers that link FBSimulatorControl directly and follow the documented HID entry point.
Reproduction
main @ c51004c, macOS 26, Xcode 27.0 beta 4 (DEVELOPER_DIR pointing at it), CoreSimulator 1169.1, booted iOS 27.0 simulator, Device Hub closed (no dtuhidd, so the indigo path is selected):
try FBSimulatorControlFrameworkLoader.essentialFrameworks.loadPrivateFrameworks(logger)
let config = FBSimulatorControlConfiguration(deviceSetPath: nil, logger: logger, reporter: nil)
let set = try FBSimulatorControl.withConfiguration(config).set
let simulator = set.simulator(withUDID: udid)!
let hid = try FBSimulatorHID(for: simulator) // throws clientClassUnavailable
With FBCONTROLCORE_DEBUG_LOGGING=1 the log confirms only CoreSimulator was ever loaded:
CoreSimulator: Already loaded, skipping
Loaded All Private Frameworks [CoreSimulator]
FAIL: clientClassUnavailable(className: "SimulatorKit.SimDeviceLegacyHIDClient")
Forcing transport: .dtuhid works fine in the same process (no SimulatorKit needed), which also confirms the rest of the stack is healthy.
Suggested fix
Load the Xcode frameworks (or at least SimulatorKit) at the top of indigo(for:) before constructing FBSimulatorIndigoHIDClient, e.g.:
static func indigo(for simulator: FBSimulator) throws -> FBSimulatorIndigoHIDTransport {
try FBSimulatorControlFrameworkLoader.xcodeFrameworks.loadPrivateFrameworks(nil)
...
or reorder so FBSimulatorIndigoHID() is constructed first. Consumers can work around it today by pre-loading xcodeFrameworks themselves before creating an FBSimulatorHID.
Summary
FBSimulatorIndigoHIDTransport.indigo(for:)evaluates its initializer arguments in declaration order:FBSimulatorIndigoHIDClient.init(for:)looks upSimulatorKit.SimDeviceLegacyHIDClientwithobjc_lookUpClass, but the thing that actually loads SimulatorKit into the process —FBSimulatorControlFrameworkLoader.xcodeFrameworks.loadPrivateFrameworks()insideFBSimulatorIndigoHID.init()— runs after it. Any process that has not already loaded SimulatorKit by other means throwsFBSimulatorHIDError.clientClassUnavailableon the entire Indigo path.This includes the default transport selection: on a simulator where
dtuhiddis not active,FBSimulator.defaultHIDTransportresolves to.indigo, so a plainFBSimulatorHID(for: simulator)fails.Why idb_companion does not hit this
FBIDBCommandExecutor.connectToHID()callsFBSimulatorControlFrameworkLoader.xcodeFrameworks.loadPrivateFrameworks(...)beforeconnectToHID, so inside the companion SimulatorKit is always resident before the client lookup runs. The bug only surfaces for consumers that linkFBSimulatorControldirectly and follow the documented HID entry point.Reproduction
main @ c51004c, macOS 26, Xcode 27.0 beta 4 (
DEVELOPER_DIRpointing at it), CoreSimulator 1169.1, booted iOS 27.0 simulator, Device Hub closed (nodtuhidd, so the indigo path is selected):With
FBCONTROLCORE_DEBUG_LOGGING=1the log confirms only CoreSimulator was ever loaded:Forcing
transport: .dtuhidworks fine in the same process (no SimulatorKit needed), which also confirms the rest of the stack is healthy.Suggested fix
Load the Xcode frameworks (or at least SimulatorKit) at the top of
indigo(for:)before constructingFBSimulatorIndigoHIDClient, e.g.:or reorder so
FBSimulatorIndigoHID()is constructed first. Consumers can work around it today by pre-loadingxcodeFrameworksthemselves before creating anFBSimulatorHID.