[BUG] macOS: main process pins one core at 100% while a wired Xbox controller is attached (Chromium XboxDataFetcher IOKit retry loop)

Status Open
Reported on v2.1.286
Maintainer reply None cached
Activity 0 comments · opened Oct 2, 2026

Summary

With an Xbox Elite Series 2 controller connected over USB, the Claude desktop app's main
(Electron browser) process spins one core at 100% for as long as the controller stays
plugged in. The spinning thread is Chromium's "Gamepad polling thread", stuck in a retry
loop trying to open the USB device through IOKit. Unplugging the controller drops the
process to idle within seconds. Reproduces deterministically on every plug-in.

The controller itself keeps working normally (macOS handles it), so the only symptom is
the wasted core: battery drain and heat on a laptop, and ~10% total CPU in Activity
Monitor on a 10-core machine.

Environment

  • Claude desktop app 2.19675.0 (Electron 44.4.3, Chromium 152.0.7977.130 per the

Electron Framework binary), Claude Code 2.1.286 running in the Code tab

  • macOS 26.7.1 (25G241), MacBook Air, Apple M5, 10 cores
  • Controller: Microsoft Xbox Elite Series 2, wired USB (idVendor 0x045E / 1118,

idProduct 0x0B00 / 2816). The same controller is also paired over Bluetooth, which does
not trigger the issue.

  • App state: Code tab with two sessions open; no in-app browser tab in use at the time.

Steps to reproduce

  1. Launch Claude desktop on macOS and leave it running.
  2. Plug an Xbox Elite Series 2 (or presumably any Microsoft USB controller in Chromium's

XboxDataFetcher product list) into the Mac by USB.

  1. Watch the Claude process (the main process, not a Helper) in Activity Monitor or

top -pid <pid>.

Expected: no measurable CPU change.
Actual: within 5 seconds the main process sits at ~100% of one core and stays there.
Unplugging the cable returns it to 0-6% immediately.

Measured on 2026-10-02:

| moment | main process CPU | hottest thread |
|---|---|---|
| controller attached for ~4 h | 98-100% | 98.5% (170 min system + 53 min user time) |
| 5 s after unplugging | 0.0 / 2.4 / 6.1% | 1.0% |
| 5 s after re-plugging | 100.1% | spinning again |
| +10 / +15 / +20 / +25 / +30 s | 101.4 / 100.3 / 100.4 / 99.6 / 100.2% | |

Diagnosis

sample <main pid> 3 while the controller was attached: of 2497 samples on the thread
named "Gamepad polling thread", 2363 were inside this path:

_CFRunLoopRunSpecificWithOptions
  __CFRunLoopDoSource1
    __CFMachPortPerform
      IODispatchCalloutFromCFMessage        (IOKit)
        <Electron Framework>                (XboxDataFetcher device-added / interest callback)
          IOCreatePlugInInterfaceForService (IOKit)
            IOUSBDeviceClass::start          (IOUSBLib)
              IORegistryEntryCreateCFProperties

Leaf frames on that thread are IOKit mach calls (io_connect_method,
io_registry_entry_get_property_bin_buf, io_service_open_extended,
io_service_add_interest_notification, io_service_close), which matches the thread's CPU
being mostly system time. The main thread and all other threads are idle (mach_msg wait).
A second 2-second sample taken right after re-plugging shows the same thread
(Thread_299107) in the same path.

Trimmed stack of that thread from the second sample (first 70 lines; full sample
output for both captures is available on request, about 440 KB each):

<details>
<summary>Gamepad polling thread, 2 s sample right after re-plugging</summary>

1657 Thread_299107: Gamepad polling thread
+ 1657 thread_start  (in libsystem_pthread.dylib) + 8  [0x187af6c1c]
+   1657 _pthread_start  (in libsystem_pthread.dylib) + 136  [0x187afbc58]
+     1657 v8::Isolate::InstallConditionalFeatures(v8::Local<v8::Context>)  (in Electron Framework) + 50444  [0x11f0e87dc]
+       1657 v8::internal::compiler::CompilationDependencies::FieldTypeDependencyOffTheRecord(v8::internal::compiler::MapRef, v8::internal::compiler::MapRef, v8::internal::InternalIndex, v8::internal::compiler::ObjectRef) const  (in Electron Framework) + 359856  [0x11ff200e0]
+         1657 v8::Value::IsObject() const  (in Electron Framework) + 42416  [0x11fa4b2b8]
+           1657 node::PrincipalRealm::inspector_disable_network_tracking() const  (in Electron Framework) + 16036  [0x11f2f4964]
+             1657 node::PrincipalRealm::inspector_disable_network_tracking() const  (in Electron Framework) + 17652  [0x11f2f4fb4]
+               1657 node::PerIsolatePlatformData::PostDelayedTaskImpl(std::__Cr::unique_ptr<v8::Task, std::__Cr::default_delete<v8::Task>>, double, v8::SourceLocation const&)  (in Electron Framework) + 3576  [0x11fcc8eac]
+                 1654 v8::internal::compiler::CompilationDependencies::PrepareInstall()  (in Electron Framework) + 310416  [0x1203b5064]
+                 ! 1644 -[NSRunLoop(NSRunLoop) runMode:beforeDate:]  (in Foundation) + 212  [0x189403c34]
+                 ! : 1614 _CFRunLoopRunSpecificWithOptions  (in CoreFoundation) + 532  [0x187c8a508]
+                 ! : | 1550 __CFRunLoopRun  (in CoreFoundation) + 2168  [0x187bb7e18]
+                 ! : | + 1550 __CFRunLoopDoSource1  (in CoreFoundation) + 504  [0x187bb9450]
+                 ! : | +   1550 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__  (in CoreFoundation) + 60  [0x187bb9528]
+                 ! : | +     1550 __CFMachPortPerform  (in CoreFoundation) + 288  [0x187be448c]
+                 ! : | +       1501 IODispatchCalloutFromCFMessage  (in IOKit) + 332  [0x18be622d0]
+                 ! : | +       ! 1493 ares_llist_node_next  (in Electron Framework) + 15229780  [0x124fbd35c]
+                 ! : | +       ! : 831 ares_llist_node_next  (in Electron Framework) + 15229000  [0x124fbd050]
+                 ! : | +       ! : | 760 ares_llist_node_next  (in Electron Framework) + 15222964  [0x124fbb8bc]
+                 ! : | +       ! : | + 607 IOCreatePlugInInterfaceForService  (in IOKit) + 1564  [0x18be6aab4]
+                 ! : | +       ! : | + ! 341 IOUSBDeviceClass::start(__CFDictionary const*, unsigned int)  (in IOUSBLib) + 144  [0x16707d6c4]
+                 ! : | +       ! : | + ! : 242 IORegistryEntryCreateCFProperties  (in IOKit) + 156  [0x18be6026c]
+                 ! : | +       ! : | + ! : | 242 io_registry_entry_get_properties_bin_buf  (in IOKit) + 160  [0x18bf027c4]
+                 ! : | +       ! : | + ! : |   242 mach_msg2_internal  (in libsystem_kernel.dylib) + 76  [0x187aca5a4]
+                 ! : | +       ! : | + ! : |     242 mach_msg2_trap  (in libsystem_kernel.dylib) + 8  [0x187ab7c34]
+                 ! : | +       ! : | + ! : 99 IORegistryEntryCreateCFProperties  (in IOKit) + 200  [0x18be60298]
+                 ! : | +       ! : | + ! :   36 IOCFUnserializeBinary  (in IOKit) + 908  [0x18be608b0]
+                 ! : | +       ! : | + ! :   + 15 __CFStringCreateImmutableFunnel3  (in CoreFoundation) + 2100  [0x187b3dde8]
+                 ! : | +       ! : | + ! :   + ! 5 _CFRuntimeCreateInstance  (in CoreFoundation) + 496  [0x187b3e790]
+                 ! : | +       ! : | + ! :   + ! : 2 _CFAllocatorAllocateImpl  (in CoreFoundation) + 12,108  [0x187c50abc,0x187c50b1c]
+                 ! : | +       ! : | + ! :   + ! : 2 v8::internal::compiler::CompilationDependencies::PrepareInstall()  (in Electron Framework) + 318052  [0x1203b6e38]
+                 ! : | +       ! : | + ! :   + ! : | 1 v8::internal::compiler::CompilationDependencies::PrepareInstall()  (in Electron Framework) + 252916  [0x1203a6fc8]
+                 ! : | +       ! : | + ! :   + ! : | + 1 v8::Object::SetLazyDataProperty(v8::Local<v8::Context>, v8::Local<v8::Name>, void (*)(v8::Local<v8::Name>, v8::PropertyCallbackInfo<v8::Value> const&), v8::Local<v8::Value>, v8::PropertyAttribute, v8::SideEffectType, v8::SideEffectType)  (in Electron Framework) + 63100  [0x11e082130]
+                 ! : | +       ! : | + ! :   + ! : | +   1 _platform_memset  (in libsystem_platform.dylib) + 180  [0x187b05144]
+                 ! : | +       ! : | + ! :   + ! : | 1 v8::internal::compiler::CompilationDependencies::PrepareInstall()  (in Electron Framework) + 316356  [0x1203b6798]
+                 ! : | +       ! : | + ! :   + ! : 1 __CFAllocatorSystemAllocateTyped  (in CoreFoundation) + 52  [0x187c509e4]
+                 ! : | +       ! : | + ! :   + ! :   1 DYLD-STUB$$malloc_default_zone  (in CoreFoundation) + 8  [0x187d37ce0]
+                 ! : | +       ! : | + ! :   + ! 5 _CFRuntimeCreateInstance  (in CoreFoundation) + 392,256,...  [0x187b3e728,0x187b3e6a0,...]
+                 ! : | +       ! : | + ! :   + ! 2 _CFRuntimeCreateInstance  (in CoreFoundation) + 840  [0x187b3e8e8]
+                 ! : | +       ! : | + ! :   + ! : 1 objc_object::changeIsa(objc_class*)  (in libobjc.A.dylib) + 76  [0x1876ab8bc]
+                 ! : | +       ! : | + ! :   + ! : 1 object_setClass  (in libobjc.A.dylib) + 36  [0x1876949a0]
+                 ! : | +       ! : | + ! :   + ! 1 _CFAllocatorAllocateImpl  (in CoreFoundation) + 220  [0x187c50b8c]
+                 ! : | +       ! : | + ! :   + ! 1 _CFRuntimeCreateInstance  (in CoreFoundation) + 472  [0x187b3e778]
+                 ! : | +       ! : | + ! :   + ! : 1 __CFAllocatorRespectsHintZeroWhenAllocating  (in CoreFoundation) + 28  [0x187b3e948]
+                 ! : | +       ! : | + ! :   + ! 1 v8::internal::compiler::CompilationDependencies::PrepareInstall()  (in Electron Framework) + 318080  [0x1203b6e54]
+                 ! : | +       ! : | + ! :   + 9 __CFStringCreateImmutableFunnel3  (in CoreFoundation) + 2768  [0x187b3e084]
+                 ! : | +       ! : | + ! :   + ! 5 __CFSearchStringROM  (in CoreFoundation) + 44  [0x187b3e348]
+                 ! : | +       ! : | + ! :   + ! : 5 __CFStrHashEightBit2  (in CoreFoundation) + 136,116  [0x187b3e444,0x187b3e430]
+                 ! : | +       ! : | + ! :   + ! 4 __CFSearchStringROM  (in CoreFoundation) + 16,56,...  [0x187b3e32c,0x187b3e354,...]
+                 ! : | +       ! : | + ! :   + 8 __CFStringCreateImmutableFunnel3  (in CoreFoundation) + 192,536,...  [0x187b3d674,0x187b3d7cc,...]
+                 ! : | +       ! : | + ! :   + 2 __CFStringCreateImmutableFunnel3  (in CoreFoundation) + 216  [0x187b3d68c]
+                 ! : | +       ! : | + ! :   + ! 2 _CFGetTSDCreateIfNeeded  (in CoreFoundation) + 12,44  [0x187b3fcf8,0x187b3fd18]
+                 ! : | +       ! : | + ! :   + 2 __CFStringCreateImmutableFunnel3  (in CoreFoundation) + 2656  [0x187b3e014]
+                 ! : | +       ! : | + ! :   +   2 _platform_memmove  (in libsystem_platform.dylib) + 0,428  [0x187b05360,0x187b0550c]
+                 ! : | +       ! : | + ! :   27 IOCFUnserializeBinary  (in IOKit) + 1396  [0x18be60a98]
+                 ! : | +       ! : | + ! :   + 8 -[__NSDictionaryM __setObject:forKey:]  (in CoreFoundation) + 392  [0x187b59f0c]
+                 ! : | +       ! : | + ! :   + ! 7 __CFStringHash  (in CoreFoundation) + 232,952,...  [0x187b430bc,0x187b4338c,...]
+                 ! : | +       ! : | + ! :   + ! 1 -[NSTaggedPointerString hash]  (in CoreFoundation) + 84  [0x187b5ab94]
+                 ! : | +       ! : | + ! :   + 7 -[__NSDictionaryM __setObject:forKey:]  (in CoreFoundation) + 444  [0x187b59f40]
+                 ! : | +       ! : | + ! :   + ! 2 -[NSTaggedPointerString isEqual:]  (in CoreFoundation) + 100  [0x187b508c4]
+                 ! : | +       ! : | + ! :   + ! : 1 _NSIsNSString  (in CoreFoundation) + 180  [0x187b509ac]
+                 ! : | +       ! : | + ! :   + ! : | 1 DYLD-STUB$$object_getMethodImplementation  (in CoreFoundation) + 12  [0x187d382b4]
+                 ! : | +       ! : | + ! :   + ! : 1 object_getMethodImplementation  (in libobjc.A.dylib) + 388  [0x187696760]
+                 ! : | +       ! : | + ! :   + ! 2 -[__NSCFString isEqual:]  (in CoreFoundation) + 160,232  [0x187b42948,0x187b42990]
+                 ! : | +       ! : | + ! :   + ! 1 -[NSTaggedPointerString isEqual:]  (in CoreFoundation) + 0  [0x187b50860]
+                 ! : | +       ! : | + ! :   + ! 1 -[__NSCFString isEqual:]  (in CoreFoundation) + 276  [0x187b429bc]
+                 ! : | +       ! : | + ! :   + ! : 1 __CFStringEqual  (in CoreFoundation) + 64  [0x187c44aec]
+                 ! : | +       ! : | + ! :   + ! 1 isEqualToString  (in CoreFoundation) + 100  [0x187b50a9c]
+                 ! : | +       ! : | + ! :   + !   1 objc_msgSend  (in libobjc.A.dylib) + 56  [0x187691838]

</details>

Likely root cause (from current Chromium source, not verified against 152 line by line)

  • On macOS, device/gamepad/gamepad_platform_data_fetcher.h adds XboxDataFetcher

unconditionally, alongside GameControllerDataFetcherMac. There is no base::Feature or
switch that disables XboxDataFetcher; the only related flag,
XboxUseGameControllerDataFetcherMac (enabled by default, and present in the Claude
binary), is not consulted inside xbox_data_fetcher_mac.cc.

  • XboxDataFetcher registers an IOKit matching notification for Microsoft product IDs

including 0x0B00 and calls TryOpenDevice for each match. Since macOS 11 the OS's own
driver (gamecontrollerd / GameController framework) holds the device, so the open fails
with exclusive access. The fetcher then registers an interest notification and retries
on kIOMessageServiceWasClosed.

  • The observed behaviour is that retry firing continuously: every callback re-runs

IOCreatePlugInInterfaceForService + IOUSBDeviceClass::start, fails, re-registers,
and the next notification arrives at once. Net effect: a busy loop driven by IOKit
messages for as long as the device is present.

  • The gamepad polling thread only exists once a renderer has bound the Gamepad service

(navigator.getGamepads() or a gamepadconnected listener). Something in the app's
renderer does this at startup, because the loop starts within seconds of plugging in,
with no user interaction in the app.

Suggested fixes

  1. App side (no Chromium change needed): make sure no renderer page in the app binds the

Gamepad API unless a feature actually needs it. If no page starts the GamepadProvider,
XboxDataFetcher is never created and the USB notifications are never registered.

  1. Upstream: have XboxDataFetcher skip Xbox product IDs when

XboxUseGameControllerDataFetcherMac is enabled (the GameController path already
handles them), or add exponential backoff / a retry cap to the exclusive-access retry.

Workaround for users

Unplug the controller, or quit Claude desktop while using a wired Xbox controller. Pairing
the controller over Bluetooth avoids the USB fetcher entirely, at the cost of Bluetooth
input latency.

View original on GitHub ↗