[Windows] Codex 26.715 enters a periodic ~15 s AppHang / ~10 s responsive cycle

Resolved 💬 20 comments Opened Jul 17, 2026 by rubiot Closed Jul 20, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

OpenAI.Codex_26.715.2305.0_x64__2p2nqsd0c76g0 (ChatGPT.exe file version 150.0.7871.124)

What subscription do you have?

Pro

What platform is your computer?

Windows 11 Pro 10.0.26200 x64; Intel Core i5-13600K; 95.75 GiB RAM; Intel UHD Graphics 770, driver 32.0.101.7085 (2026-03-02)

What issue are you seeing?

After the Microsoft Store updated Codex from the 26.707 line to the 26.715 line, the main Codex window began entering a stable periodic freeze cycle. The window becomes completely non-responsive for about 15 seconds, recovers for about 10 seconds, and freezes again.

The rest of Windows remains responsive. Task Manager shows the ChatGPT-named Codex window process as "Não respondendo" (Not responding), but does not show high CPU, RAM, disk, network, or GPU use.

Windows Error Reporting recorded 38 AppHangTransient events for ChatGPT.exe file version 150.0.7871.124 in a seven-day window. A live 40.801-second WM_NULL probe observed three unresponsive episodes (3.636 s, 15.079 s, and another episode active when the capture ended), with 10.125 seconds of responsiveness between episodes. Healthy message latency averaged 0.261 ms.

During the 15.079-second episode, Wait Chain Traversal reported the native UI thread as Blocked, with no deadlock cycle and no advancing user/kernel CPU counters. This indicates a sleeping/blocked main UI thread rather than a CPU-bound loop.

What steps can reproduce the bug?

  1. Start Codex Desktop on Windows.
  2. Open or continue a normal thread.
  3. Leave the main window open; no special action is required.
  4. Observe the window repeatedly enter "Not Responding."
  5. Wait approximately 15 seconds; it recovers.
  6. After approximately 10 seconds, it becomes non-responsive again.

The problem survives restarting both Codex and Windows.

What is the expected behavior?

The Codex window should remain responsive and continue processing Windows messages while background work, rendering, or network operations are in progress.

Additional information

Regression timing:

  • No Codex AppHangTransient events were present in the preceding days.
  • 26.715.2236.0 deployment began at 2026-07-16 22:26:51 -03:00.
  • The first AppHangTransient occurred at 2026-07-16 22:56:04 -03:00.
  • 26.715.2305.0 deployment began at 2026-07-16 23:21:46 -03:00, and the issue continues on that build.

No matching display-driver resets, WHEA errors, disk errors, or resource-exhaustion events were found since boot.

Possible related reports: #23981, #20214, #16374, and #20867.

I am attaching a sanitized diagnostics bundle containing package/OS/driver inventory, WER timestamps, Store deployment timeline, process counters, response-probe timings, hang summaries, UI-thread/WCT snapshots, whitelisted app-log signal names, the Task Manager screenshot, and the reusable collector script. It contains no conversation text, raw Codex logs, source code, command lines, window titles, cookies, tokens, credentials, crash dumps, or process memory.

codex-hang-diagnostics.zip

View original on GitHub ↗

20 Comments

github-actions[bot] contributor · 3 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #33873

Powered by Codex Action

rubiot · 3 days ago

I captured an ETW/WPR trace during the periodic freeze and correlated it with the JavaScript/native sources shipped inside app.asar. The root cause appears to be synchronous HID enumeration for the Codex Micro / Work Louder integration on Electron's main thread.

Captured hang

  • Package: OpenAI.Codex_26.715.2305.0_x64__2p2nqsd0c76g0
  • Process: ChatGPT.exe (PID 5020)
  • Main/window thread: CrBrowserMain (TID 38484)
  • First failed window-response probe: 9.600 s after capture start
  • ETW wait interval: approximately 9.3628467–14.3669202 s
  • Wait duration: 5.0040735 s
  • CPU time used by PID 5020 in the 9–16 s analysis window: only about 11 ms
  • The wait was readied by Idle (0), consistent with synchronous device-I/O completion/timeout rather than CPU saturation.

Resolved portion of the blocking stack:

ChatGPT.exe
  chrome.dll
    HID.node
      hid.dll!HidD_GetManufacturerString
      hid.dll!DeviceIoControlHelper
      kernel32.dll!DeviceIoControlImplementation
      KernelBase.dll!DeviceIoControl
      ntdll.dll!ZwDeviceIoControlFile
      ntoskrnl.exe!NtDeviceIoControlFile
      ntoskrnl.exe!IopSynchronousServiceTail
      HIDCLASS.SYS!HidpIrpMajorDeviceControl
      HIDCLASS.SYS!HidpGetDeviceString
      HIDCLASS.SYS!HidpCallDriver
      hidusb.sys!HumInternalIoctl
      hidusb.sys!HumGetStringDescriptor
      hidusb.sys!HumGetDescriptorRequest
      hidusb.sys!HumCallUSB
      ntoskrnl.exe!KeWaitForSingleObject

Correlation with shipped code

The package includes:

  • @worklouder/device-kit-oai 0.1.10
  • @worklouder/wl-device-kit 0.1.18
  • node-hid 3.3.0

In .vite/build/codex-micro-service-C0OetNTY.js:

  • the default reconnect delay is 1e4 (10,000 ms);
  • connect() calls this.discovery.findWLDevices([DeviceType.Project2077]);
  • if no device is found, scheduleReconnect() repeats the operation after that 10-second delay.

In wl-device-kit/dist/index.js, findWLDevices() calls synchronous nodeHID.devices() and only filters the returned list afterward. Thus it enumerates unrelated HID interfaces too.

In the shipped node-hid source, devices() invokes hid_enumerate() synchronously. During enumeration, the Windows HIDAPI implementation calls HidD_GetManufacturerString(), which is exactly where the trace shows the 5-second USB descriptor wait. The same shipped node-hid version already exposes devicesAsync().

This explains both symptoms: a roughly 10-second recurrence interval and a nonresponsive window with near-zero CPU while a USB HID interface times out. The problematic peripheral does not have to be a Codex Micro, because all HID devices are enumerated before filtering.

Suggested fix

  1. Do not call nodeHID.devices() on Electron's main thread; use await nodeHID.devicesAsync() or a worker/utility process.
  2. Filter by the Work Louder VID/expected PIDs before requesting string descriptors from unrelated HID interfaces.
  3. Prefer device-arrival notification or exponential backoff over fixed 10-second polling.
  4. Provide a feature flag/setting to disable Codex Micro discovery.

Source-map note

The ASAR has 6,818 entries and 3,561 JavaScript files with sourceMappingURL references, but contains zero .map files; all 3,561 map references are missing. The distributed bundles and native-module sources were nevertheless sufficient to identify this path.

ASAR SHA-256: d909924d6ae7a160ac78b88f01f9b16f079e6abbe3f677427b752a411c6a3449

I kept the raw ETL local because it contains process/module/thread/path metadata, but I can provide a reduced export or additional targeted evidence if maintainers need it.

rubiot · 3 days ago

Temporary local workaround (validated, but unsupported)

I found a reversible workaround that stops the freezes without disconnecting or disabling any USB device. It disables only Codex Micro discovery for the launched Codex process, preventing the synchronous HID enumeration described above.

Tested with OpenAI.Codex_26.715.2305.0_x64:

| Launch | Probe duration | Samples | Window timeouts | Longest estimated hang |
| --- | ---: | ---: | ---: | ---: |
| Normal | 30.18 s | 110 | 70 | 13.3 s |
| With workaround | 30.21 s | 116 | 0 | 0 s |

The hook was also instrumented locally, and the real Electron main process confirmed that WLDeviceDiscovery.findWLDevices() had been patched before the second measurement.

Important caveats
  • This is not an official fix. It relies on the current package's internal module name and may stop working after an update.
  • Codex Micro hardware will be unavailable for that launch.
  • NODE_OPTIONS=--require executes JavaScript inside the application process. Only use a hook whose contents you have inspected. The complete hook is shown below.
  • This does not modify the installed MSIX, Registry, drivers, USB devices, or global environment variables.
  • To revert, quit Codex and start it normally from the Start menu.
1. Save this as disable-codex-micro-hook.cjs
"use strict";

if (process.env.CODEX_DISABLE_MICRO_DISCOVERY === "1") {
  const Module = require("node:module");
  const marker = Symbol.for("codex.workaround.micro-discovery-disabled");

  if (!Module[marker]) {
    const originalLoad = Module._load;
    Object.defineProperty(Module, marker, { value: true });

    Module._load = function codexMicroWorkaroundLoad(
      request,
      parent,
      isMain,
    ) {
      const loaded = originalLoad.call(this, request, parent, isMain);

      if (request === "@worklouder/device-kit-oai") {
        const prototype = loaded?.WLDeviceDiscovery?.prototype;

        if (
          prototype &&
          typeof prototype.findWLDevices === "function" &&
          !prototype[marker]
        ) {
          Object.defineProperty(prototype, marker, { value: true });
          prototype.findWLDevices = function findNoCodexMicroDevices() {
            return [];
          };
        }
      }

      return loaded;
    };
  }
}
2. Save this beside it as Start-Codex-Without-Micro.ps1
$ErrorActionPreference = 'Stop'

$hookPath = Join-Path $PSScriptRoot 'disable-codex-micro-hook.cjs'
if (-not (Test-Path -LiteralPath $hookPath -PathType Leaf)) {
    throw "Hook not found: $hookPath"
}

if (@(Get-Process -Name 'ChatGPT' -ErrorAction SilentlyContinue).Count -gt 0) {
    throw 'Quit ChatGPT/Codex completely, including the tray process, and retry.'
}

$package = Get-AppxPackage -Name 'OpenAI.Codex' |
    Sort-Object Version -Descending |
    Select-Object -First 1
if ($null -eq $package) {
    throw 'The OpenAI.Codex package was not found.'
}

$executablePath = Join-Path $package.InstallLocation 'app\ChatGPT.exe'
$requireOption = '--require="' + $hookPath.Replace('\', '/') + '"'
$previousDisable = $env:CODEX_DISABLE_MICRO_DISCOVERY
$previousNodeOptions = $env:NODE_OPTIONS

try {
    $env:CODEX_DISABLE_MICRO_DISCOVERY = '1'
    $env:NODE_OPTIONS = if ([string]::IsNullOrWhiteSpace($previousNodeOptions)) {
        $requireOption
    } else {
        "$previousNodeOptions $requireOption"
    }

    Start-Process -FilePath $executablePath
}
finally {
    $env:CODEX_DISABLE_MICRO_DISCOVERY = $previousDisable
    $env:NODE_OPTIONS = $previousNodeOptions
}
3. Launch it

First quit Codex completely, including its system-tray process. Then run:

powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Start-Codex-Without-Micro.ps1

The launcher sets both variables only in its own process environment; the started Codex process inherits them, while the caller's environment is restored immediately afterward.

whatthecodemean · 2 days ago

Resolution update — 2026-07-20, package OpenAI.Codex_26.715.7063.0_x64

On the same Windows 11 machine, with the same USB/HID devices connected, I repeated the test using a normal Explorer launch with no No-Micro workaround:

  • 3,021 samples at 100 ms across two consecutive captures
  • 302.9 seconds of sampled time
  • multiple sustained voice-input sessions
  • 0 hung-window episodes
  • captured stop-click-to-microphone-release times: 0.027 s, 0.042 s, and 0.083 s
  • no new ChatGPT.exe AppHang event
  • no audio, transcription text, prompts, or window titles recorded

The new Windows build still ships the nested Work Louder node-hid/build/Release/HID.node, but it was not loaded into the normal-launch main process during this test. Instead, the process loaded resources/native/hid-topology-watcher.node, and the Windows codex-micro-service path now calls findCodexMicroInterfaces(). The previous broad node-hid.devices() path was not used.

This is consistent with an app-side fix for the blocking HID-enumeration path. I can confirm the issue is resolved on this machine in 26.715.7063.0; cross-machine confirmation is still appropriate before the issue itself is closed.

---

Historical A/B evidence from package 26.715.3651.0

I repeated a controlled local A/B test on the same machine and thread. The sampler ran every 100 ms and recorded only window-hung state, process counters, microphone capability start/stop timestamps, and foreground mouse-click timestamps. It did not record audio, transcription text, prompts, or window titles.

A — normal launch (5 minutes)

  • 2,985 samples
  • 7 hung episodes, each 4.911–5.425 s
  • five inter-start intervals were 20.058–20.561 s; one gap was 30.089 s
  • one stop-dictation click landed immediately before a hung interval; Windows released the microphone 6.912 s later
  • stop clicks outside a hung interval released the microphone in 0.032–0.122 s

B — same build with a local unsupported No-Micro startup hook (5 minutes)

The hook only stubs @worklouder/device-kit-oai as “no device found.”

  • 2,991 samples
  • 4 voice sessions
  • 0 hung episodes
  • captured stop-to-microphone-release times: 0.013 s, 0.095 s, and 0.036 s (one stop click was not captured)
  • voice input and transcription still worked

The issue author has already captured an ETW/WPR blocking stack through HID.node -> hid.dll!HidD_GetManufacturerString -> DeviceIoControl -> HIDCLASS.SYS/hidusb.sys, so the native blocking path is documented in this issue. I have not collected a second ETW trace on my machine and therefore cannot independently prove that my machine entered the identical native stack. However, the same-build A/B result is consistent with that documented root cause and independently shows that disabling only Work Louder/Codex Micro discovery removes the hangs while voice input remains functional.

That package shipped both the Work Louder node-hid\build\Release\HID.node component and the serialport bindings.node component. The existing ETW evidence implicated the HID enumeration path; I did not attribute that hang to serialport.

rubiot · 2 days ago

Version 26.715.31925 still has the issue.

zagahlo · 2 days ago

Version 26.715.4045.0 has the issue also.

craiga24 · 2 days ago

May help someone: I have been having the same problem after hours of pulling my hair out it turned out to be my K55 corsair keyboard causing the issues. I unplugged it an plugged in a wireless keyboard and no hang or crash in 2 hours (this only started happening after the latest update for me)

VVi3ard · 2 days ago

I can confirm that switching the keyboard from a USB 3.0 port to a USB 2.0 port helped; I have a standard Chinese USB keyboard.
It was connected via a USB 3.0 hub. I plugged it directly into the PC (USB 2.0), and the problem went away completely.

littleshrekguy-code · 2 days ago

Confirming this on Windows 10 (issue reports Win 11) with a different
26.715 build
, and adding a thread-level detail that may narrow the cause.

Environment

  • Windows 10 Pro 10.0.19045, 16 GB RAM
  • Codex Desktop MSIX/Store package OpenAI.Codex_26.715.3651.0_x64__2p2nqsd0c76g0
  • Also occurred on 26.715.21425 (core 0.145.0)

Same regression window: zero freezes across 15 logged sessions
2026-07-05 → 2026-07-16; freezes began 2026-07-17, immediately after
26.715.21425 installed on 2026-07-16 20:42 local. Matches your
"first AppHangTransient 2026-07-16 22:56:04".

Same cycle, longer period. Mine is ~65–70s frozen / few seconds responsive,
vs your 15s/10s — so the period varies by machine but the pattern is identical.
Sampled every 10s on one launch (PID 25624):

The detail I think matters: it isn't just "blocked/sleeping" — the UI thread
is specifically suspended, and it's the only one. Enumerating all 56
threads of the hung ChatGPT.exe while frozen:

  • main/UI thread: ThreadState=Wait, WaitReason=Suspended
  • other ~55 threads: normal UserRequest / EventPairLow waits
  • suspended thread accrues 0.00s CPU over a 6s sample — externally parked,

not deadlocked, not busy

A lock/deadlock shows as a blocking wait, not a suspend. Windows PLM/lifecycle
freeze would suspend every thread and show "Suspended" in Task Manager, not
"Not Responding". One surgically-suspended UI thread suggests something
in-process calling SuspendThread on it — possibly a hang-watcher, profiler,
or telemetry sampler. Native modules loaded in the process:
windows-updater.node, windows-account.node, HID.node
(@worklouder/device-kit-oai), plus Sentry.

Also ruled out on my machine: memory pressure (5+ GB free), third-party AV
(Defender only), WerFault (idle, no crash dumps, no Crashpad reports, no Event
Viewer entries for the app), GPU, port conflicts. Reinstall, app-data clear,
and the Background-apps permission toggle made no difference.

Backend is unaffected — the Rust codex.exe app-server keeps serving
JSON-RPC and logging normally the entire time the UI is parked. The Electron UI
log stops mid-stream; when suspension hits early enough in startup the UI log
is 0 bytes.

Happy to capture a minidump mid-freeze if that would help identify the
suspending caller.

Table dropped from my comment above — here's the sampled cycle:

time responding mainThread mainCPU procCPU
16:06:23 False Suspended 7.20 11.61
16:06:33 False Suspended 7.20 11.61 <- 0 CPU in 10s
16:06:43 True Running 7.64 13.09
16:06:53 False Suspended 11.09 17.44
16:07:58 False Suspended 11.09 17.70 <- ~65s parked, 0 CPU
16:08:08 True Running 14.97 22.20
16:08:21 True Running 20.08 30.19 <- near-pegged once released

mainCPU frozen at a constant value across each parked stretch is the key part —
the thread isn't slow, it's stopped.
tried to use claude code to fix, this is what it gave me.

visionsofparadise · 1 day ago

Root-caused this on my machine (26.715.4045.0, Win11 26200), it's synchronous HID device I/O on the browser-process UI thread.

Minidump of the window-owning thread during a hang:

ntdll!ZwDeviceIoControlFile
KernelBase!DeviceIoControl
hid.dll!HidD_SetFeature / HidD_GetProductString / HidD_GetSerialNumberString
HID.node (node-hid, bundled via app.asar.unpacked\node_modules\@worklouder\device-kit-oai\...\wl-device-kit\...\node-hid)
chrome.dll ...
user32!CallWindowProcW
The device-kit probes every HID interface with blocking HidD_* calls inside window-message dispatch, with no timeout. Any HID device that answers control-pipe requests slowly (or not at all) blocks the UI thread for the duration — CPU stays flat, WCT shows no deadlock cycle, and the hang repeats on every device-change event or probe cycle, which produces the periodic hang/responsive pattern reported here.

In my case the trigger was a USB keyboard (Corsair K63) whose HID interfaces had stopped servicing control requests entirely (typing still worked, so nothing looked wrong). Timing HidD_GetProductString per HID interface found it instantly: every interface of that device blocked >10s while all others answered in ~10-40ms. Unplugging/replugging the device cleared the hangs completely.

Suggested fix: run HID enumeration/probing off the UI thread and/or add timeouts. Same code ships in the ChatGPT desktop app.

zagahlo · 1 day ago

I can confirm that unplugging the keyboard while Codex is unresponsive and reconnecting it to a different USB port only solves the issue temporarily; the responsiveness-unresponsiveness cycle quickly starts again.

visionsofparadise · 1 day ago

Same here, replugging only bought minutes. That's the pinpoint: a replug fires a device-arrival event, and the device-kit re-enumerates HID on every such event, on the UI thread, with blocking HidD_* calls. Reconnecting the keyboard immediately re-runs the probe cycle that stalls the device, so the hang cycle resumes.

The loop: device-change event, then @worklouder/device-kit-oai (bundled node-hid) enumerates synchronously on the UI thread, then a DeviceIoControl blocks on the slow HID interface and the window freezes until it completes. The fix has to be in code: probe off the UI thread, with timeouts.

ranran141 · 1 day ago

Culprit device identified and workaround confirmed: JBL Pebbles USB speakers (VID_05FC PID_0231) — consumer-control HID interface times out at exactly ~5 s on the manufacturer-string request
Same regression on OpenAI.Codex_XX, Windows 11. Symptoms matched this thread: a guaranteed freeze at every startup plus a periodic hang cycle even when fully idle. During each hang, dragging the Codex window halted while the composer caret kept blinking (main/browser process stalled, renderer alive), and input was affected system-wide (cursor moved, clicks dead everywhere).
Ruled out first: clean reinstall + full .codex deletion, IME (same with ENG-US layout), GPU/VRAM contention, non-ASCII profile path. Unplugging a Stream Deck made no difference.
Then I probed every HID interface on the system directly with timed HidD_GetManufacturerString / HidD_GetProductString / HidD_GetSerialNumberString calls. Every interface answered in 0–110 ms except one:
HID\VID_05FC&PID_0231&MI_04 (JBL Pebbles USB speakers — consumer-control interface for the volume dial)
Manufacturer string: 5014 ms Product: 12 ms Serial: 10 ms
The device simply never answers the manufacturer-string request and the call runs into the ~5-second USB control-transfer timeout — matching the ~5 s hang episodes measured earlier in this thread. The speakers otherwise work perfectly and have for years; the firmware just doesn't implement that one request.
Workaround (confirmed): disabling only that HID interface —
powershellGet-PnpDevice | ? { $_.InstanceId -like "VID_05FC&PID_0231&MI_04" } | Disable-PnpDevice -Confirm:$false
— eliminates all freezes completely, including the startup hang and the system-wide input lockups. Audio is unaffected (only the hardware volume dial loses its Windows integration).
This is a concrete reproduction of the diagnosis above: one slow/non-answering HID device × synchronous HidD_* string requests on the UI thread × no timeout = a 5-second UI freeze on every enumeration pass. Probing off the UI thread with per-call timeouts would make this entire class of device harmless. Happy to run an instrumented build against this device if useful.

davidcfmt · 1 day ago

Adding to this, hoping to get a fix completed sooner.

I am experiencing the same regression after upgrading to the newest Windows app.

Symptoms:

  • The UI repeatedly freezes and shows “Not Responding.”
  • Codex work can continue in the background while the UI is frozen.
  • The issue also occurs in a brand-new session while no task is running.
  • The app may become stuck on an endless loading spinner after relaunch.
  • I cannot switch projects or reliably reach Settings.

Troubleshooting attempted:

  • Windows Repair
  • Windows Reset
  • Full uninstall
  • Windows restart
  • Reinstall of the latest app package
  • Testing a new session

None resolved the issue.

Codex CLI 0.144.6 works normally, so the failure appears isolated to the Windows desktop UI.

TheodorKleynhans · 1 day ago

I hit the same hang cycle on 26.715 (Windows 11 26200, Codex 26.715.4045.0) and managed to root-cause it on my machine. TL;DR: the main Electron thread synchronously enumerates HID devices via node-hid, and a single misbehaving HID device turns each string-descriptor query into a ~5 s driver-timeout wait — producing exactly the 15–30 s "Not Responding" cycles described above.

Evidence

I captured a full minidump of the main ChatGPT.exe process (non-invasive cdb -pv attach) during the hang cycle. Thread 0 — the native UI thread that WCT reports as Blocked — was here:

ntdll!NtWaitForSingleObject
KernelBase!WaitForSingleObjectEx
KernelBase!GetOverlappedResult
hid!DeviceIoControlHelper
hid!HidD_GetSerialNumberString
HID.node (node-hid native module, several frames)
chrome!... (V8 / N-API frames)
chrome!uv_run          <-- Electron main loop
chrome!ChromeMain
ChatGPT.exe

So the app is running node-hid enumeration (which reads manufacturer/product/serial string descriptors per HID collection) on the UI thread, and HidD_Get*String has no timeout — it blocks until the device's driver gives up.

The offending device (on my system)

A FIFINE K670 USB microphone (USB\VID_3142&PID_0001) had gotten into a wedged state (its audio interface reported Code 10 "device cannot start"). Its HID interface exposes 2 collections, and every string query against either collection blocked for 5,009 ms before failing, while the other 22 HID interfaces on the system answered in under 40 ms. Two collections × three string queries × 5 s per enumeration pass matches the observed freeze length; the app re-enumerates periodically, which produces the freeze/recover cycle.

After unplugging that device: zero AppHangTransient events since, app fully responsive. Replugging (power-cycling the device) also clears the wedged state.

Script to find your offending device

For anyone else hitting this: the PowerShell script below probes every HID interface with the same string queries node-hid makes and times them. Healthy devices answer in milliseconds; the culprit blocks for seconds. It prints each device path before touching it, so even a device that hangs forever is identified by the last printed line. Read-only (opens devices with access mask 0), no elevation needed.

<details>
<summary><code>Find-SlowHidDevice.ps1</code></summary>

# Probes every HID interface with the same string queries node-hid's
# enumeration performs (HidD_GetProductString / HidD_GetSerialNumberString)
# and reports how long each device takes to answer. A healthy device answers
# in a few ms; a wedged device blocks for ~5s (driver timeout) per query.
# Each device path is printed BEFORE it is touched, so even if a query hangs
# forever, the last printed line names the offending device.

Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;

public static class HidProbe {
    [DllImport("hid.dll")] public static extern void HidD_GetHidGuid(out Guid g);
    [DllImport("hid.dll", CharSet=CharSet.Unicode)] public static extern bool HidD_GetSerialNumberString(IntPtr h, byte[] buf, int len);
    [DllImport("hid.dll", CharSet=CharSet.Unicode)] public static extern bool HidD_GetProductString(IntPtr h, byte[] buf, int len);
    [DllImport("setupapi.dll", CharSet=CharSet.Unicode)] public static extern IntPtr SetupDiGetClassDevs(ref Guid g, IntPtr e, IntPtr hwnd, int flags);
    [StructLayout(LayoutKind.Sequential)]
    public struct SP_DEVICE_INTERFACE_DATA { public int cbSize; public Guid guid; public int flags; public IntPtr reserved; }
    [DllImport("setupapi.dll", CharSet=CharSet.Unicode)] public static extern bool SetupDiEnumDeviceInterfaces(IntPtr h, IntPtr di, ref Guid g, int idx, ref SP_DEVICE_INTERFACE_DATA d);
    [DllImport("setupapi.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr h, ref SP_DEVICE_INTERFACE_DATA d, IntPtr detail, int size, out int req, IntPtr devinfo);
    [DllImport("setupapi.dll")] public static extern bool SetupDiDestroyDeviceInfoList(IntPtr h);
    [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] public static extern IntPtr CreateFile(string name, uint access, uint share, IntPtr sec, uint disp, uint flags, IntPtr tmpl);
    [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr h);
}
'@

$hidGuid = [Guid]::Empty
[HidProbe]::HidD_GetHidGuid([ref]$hidGuid)
# DIGCF_PRESENT (0x2) | DIGCF_DEVICEINTERFACE (0x10)
$devs = [HidProbe]::SetupDiGetClassDevs([ref]$hidGuid, [IntPtr]::Zero, [IntPtr]::Zero, 0x12)

$suspects = @()
$idx = 0
while ($true) {
    $ifd = New-Object HidProbe+SP_DEVICE_INTERFACE_DATA
    $ifd.cbSize = [Runtime.InteropServices.Marshal]::SizeOf([type][HidProbe+SP_DEVICE_INTERFACE_DATA])
    if (-not [HidProbe]::SetupDiEnumDeviceInterfaces($devs, [IntPtr]::Zero, [ref]$hidGuid, $idx, [ref]$ifd)) { break }

    $req = 0
    [void][HidProbe]::SetupDiGetDeviceInterfaceDetail($devs, [ref]$ifd, [IntPtr]::Zero, 0, [ref]$req, [IntPtr]::Zero)
    $buf = [Runtime.InteropServices.Marshal]::AllocHGlobal($req)
    [Runtime.InteropServices.Marshal]::WriteInt32($buf, 8)   # cbSize of DETAIL_DATA on x64
    $path = $null
    if ([HidProbe]::SetupDiGetDeviceInterfaceDetail($devs, [ref]$ifd, $buf, $req, [ref]$req, [IntPtr]::Zero)) {
        $path = [Runtime.InteropServices.Marshal]::PtrToStringUni([IntPtr]::Add($buf, 4))
    }
    [Runtime.InteropServices.Marshal]::FreeHGlobal($buf)
    if (-not $path) { $idx++; continue }

    Write-Host "[$idx] probing $path"

    $t = [Diagnostics.Stopwatch]::StartNew()
    $h = [HidProbe]::CreateFile($path, 0, 3, [IntPtr]::Zero, 3, 0, [IntPtr]::Zero)
    $openMs = $t.ElapsedMilliseconds
    if ($h -eq [IntPtr]::new(-1)) {
        Write-Host "      open failed after $openMs ms (often just an exclusive device; not necessarily the culprit)"
        $idx++; continue
    }

    $strbuf = New-Object byte[] 512
    $t.Restart()
    $ok = [HidProbe]::HidD_GetProductString($h, $strbuf, 512)
    $prodMs = $t.ElapsedMilliseconds
    $prod = if ($ok) { [Text.Encoding]::Unicode.GetString($strbuf).TrimEnd([char]0) } else { '<no answer>' }

    $t.Restart()
    [void][HidProbe]::HidD_GetSerialNumberString($h, $strbuf, 512)
    $serMs = $t.ElapsedMilliseconds
    [void][HidProbe]::CloseHandle($h)

    Write-Host ("      open {0} ms | product '{1}' {2} ms | serial query {3} ms" -f $openMs, $prod, $prodMs, $serMs)
    if ($openMs -gt 500 -or $prodMs -gt 500 -or $serMs -gt 500) {
        Write-Host "      ^^^ SLOW - likely offender" -ForegroundColor Red
        $suspects += $path
    }
    $idx++
}
[void][HidProbe]::SetupDiDestroyDeviceInfoList($devs)

Write-Host ""
if ($suspects) {
    Write-Host "Slow HID interfaces found:" -ForegroundColor Red
    foreach ($s in $suspects) {
        Write-Host "  $s"
        if ($s -match 'vid_([0-9a-f]{4}).pid_([0-9a-f]{4})') {
            $vid = $Matches[1]; $pid_ = $Matches[2]
            Get-PnpDevice -ErrorAction SilentlyContinue |
                Where-Object InstanceId -match "VID_$vid&PID_$pid_" |
                Format-Table Status, Class, FriendlyName, InstanceId -AutoSize
        }
    }
    Write-Host "Unplug/replug the device (or disable its HID interface in Device Manager) and the hangs should stop."
} else {
    Write-Host "No slow HID interfaces found - every device answered promptly."
}

</details>

Suggested app-side fix

The device was the trigger, but the regression is in the app: 26.707 didn't freeze with the same device attached. HID enumeration (and especially the per-device string-descriptor reads) should run off the main thread — or skip string queries entirely during enumerate — since any flaky/wedged HID device on the user's system otherwise stalls the whole UI with no timeout.

Hope this helps narrow it down — happy to provide the full dump analysis output if useful.

Naoki0513 · 18 hours ago

Additional confirmed reproduction on a newer Windows Store package.

Environment:

  • Codex package: OpenAI.Codex_26.715.4045.0_x64__2p2nqsd0c76g0
  • ChatGPT.exe file version: 150.0.7871.124
  • Desktop-reported internal client version in local diagnostics: 26.715.31925
  • Windows 11 x64
  • 32 GB RAM; no system-wide memory, disk, GPU, or paging pressure

Observed on 2026-07-20 JST:

  • The parent ChatGPT.exe alternates between responsive and Responding=False on an approximately 15-second cadence.
  • A 39-second sample showed: unresponsive -> responsive after ~15 s -> unresponsive after another ~15 s.
  • CPU and memory remained moderate during the cycle.
  • The main window UI thread was the only thread reported in the suspicious wait state; the other 55 threads were not suspended. The kernel-reported suspend count for the UI thread remained 0, so this does not appear to be an external process suspension.

Important A/B result:

  • I preserved the original long/tool-heavy thread and created a brand-new projectless thread containing only one short assistant sentence.
  • After navigating the Windows desktop app to that new thread, the same periodic freeze reproduced immediately.
  • Over 40 one-second samples (44.14 s elapsed because the probe itself was delayed during hangs), 30/40 samples reported Responding=False.
  • Transitions were: unresponsive at start, responsive at ~15.03 s, unresponsive again at ~30.04 s.
  • Therefore, a long/old thread may amplify other UI problems, but it is not required for this periodic 26.715 hang.

Troubleshooting with no improvement:

  • Restarted Codex and Windows.
  • Stopped unrelated Chrome/OBS workloads.
  • Disabled all Codex plugins for an A/B test, then restored them.
  • Stopped the in-app-browser renderer.
  • Backed up and regenerated Chromium UI caches.
  • Backed up and regenerated a 421 MB logs_2.sqlite diagnostic DB.
  • Switched conversationDetailMode from STEPS_COMMANDS to STEPS_PROSE for an A/B test, then restored it.
  • None changed the ~15-second periodic cycle.

This confirms that the issue is still present in package 26.715.4045.0, not only 26.715.2305.0. I can provide a sanitized response-transition timeline or run a targeted collector if maintainers need another data point.

Naoki0513 · 18 hours ago

Root cause confirmed on my machine: the offending HID device is a HyperX SoloCast microphone (VID_03F0&PID_0B8B).

Read-only per-interface timing before disconnect:

  • MI_02 / COL01: manufacturer 4,994–5,005 ms; product 5,000–5,016 ms
  • MI_02 / COL02: manufacturer 5,009–5,011 ms; product 5,015–5,017 ms
  • Serial queries returned immediately.
  • The other 28 opened HID interfaces were below the 500 ms threshold.

The SoloCast audio interface is MI_00; the slow paths are the separate control HID interface MI_02, which exposes consumer-control and vendor-defined collections.

After physically disconnecting the HyperX SoloCast:

  • The device disappeared from the present HID inventory.
  • First Codex window check: 45/45 one-second samples were responsive.
  • Second consecutive check: 30/30 one-second samples were responsive.
  • There were no Responding=False transitions, whereas the connected-device baseline reliably alternated approximately every 15 seconds.

This confirms the app regression is synchronous HID string-descriptor probing on the Electron main thread. A normally functioning USB audio device with slow/unsupported HID descriptor responses can freeze the entire Codex UI periodically. I will also test whether a physical unplug/replug resets the SoloCast HID behavior or whether disabling only MI_02 is required.

Naoki0513 · 18 hours ago

Follow-up with a confirmed recovery condition on Windows Codex 26.715.4045.0:

  • Before recovery, the HyperX SoloCast control HID interfaces (VID_03F0&PID_0B8B, MI_02, COL01/COL02) took about 5 seconds for each Manufacturer/Product string query.
  • Unplugging the SoloCast removed the periodic UI freezes completely (45/45 and 30/30 one-second samples remained responsive).
  • After physically reconnecting/power-cycling the SoloCast, the same HID queries completed in 0–5 ms (maximum 5 ms).
  • A fresh 45-second measurement with the microphone connected remained responsive for 45/45 samples (Responding=False: 0).

So a physical USB power-cycle cleared the wedged device state in this instance. The app-side regression/risk remains: synchronous enumeration/string reads of unrelated HID devices can still block the Windows UI for many seconds if a device or driver stalls. A timeout, async enumeration, or filtering to known Codex Micro VID/PID devices would prevent recurrence.

visionsofparadise · 11 hours ago

Confirming that I am no longer experiencing this issue in Version 26.715.52143

rubiot · 9 hours ago

Confirmed fixed in internal version 26.715.52143

I inspected the updated Windows package and repeated the responsiveness test without the temporary NODE_OPTIONS workaround. The fix is present, so I am closing this issue as resolved.

Exact version mapping
  • Installed MSIX: OpenAI.Codex_26.715.7063.0_x64
  • Internal application version from app.asar/package.json: 26.715.52143
  • The current app process was launched normally. Its PID did not appear in the workaround activation log, whose last entry belonged to the previous process before this launch.
Implementation change

The old Windows discovery path ran in the Electron main process:

CodexMicroService.connect()
  -> WLDeviceDiscovery.findWLDevices()
  -> nodeHID.devices()
  -> synchronous enumeration of every HID interface
  -> HidD_GetManufacturerString/ProductString/SerialNumberString

If no Codex Micro was found, it repeated that synchronous scan every 10 seconds.

In internal version 26.715.52143, the Windows path no longer calls findWLDevices()/nodeHID.devices() for discovery. CodexMicroService now awaits a dedicated native operation:

await findCodexMicroInterfaces()

The new hid-topology-watcher.node addon:

  • uses napi_create_async_work and napi_queue_async_work;
  • uses CM_Register_Notification for event-driven topology changes;
  • enumerates through CM_Get_Device_Interface_ListW;
  • uses targeted HID attributes/capabilities;
  • does not import HidD_GetManufacturerString, HidD_GetProductString, or HidD_GetSerialNumberString, which were the calls observed blocking for approximately five seconds.

The fixed ten-second blind discovery loop is gone. Normal discovery is topology-event driven. A 30-second fallback scan exists only if watcher creation fails, and topology settle retries after an actual change are bounded to 250 ms, 1 s, and 3 s.

For Linux, the new implementation uses nodeHID.devicesAsync(VID, PID).

Runtime verification

Passive probe of the normally launched, unmodified app:

| Internal version | Duration | Samples | Window timeouts | Longest estimated hang |
| --- | ---: | ---: | ---: | ---: |
| 26.715.2305 | 30.18 s | 110 | 70 | 13.3 s |
| 26.715.52143 | 30.20 s | 115 | 0 | 0 s |

This agrees with the independent user confirmation above. The temporary workaround is no longer required on this version.