[Windows 10] DWM Composition handles accumulate after Codex tasks with tool calls

Open 💬 4 comments Opened Jul 14, 2026 by J-ShuJie

Summary

On Windows 10 22H2, I can reproduce persistent growth in DWM's Composition handle count after Codex tasks that make terminal tool calls. A no-tool task that replied only OK did not show the same growth. In one controlled five-call run, the Composition count increased by 22 and remained elevated throughout the 30-second period after the task finished.

After about four days of normal Codex use, DWM had 22,770 Composition handles, compared with 817 after a DWM restart. At that point, dragging an ordinary window could lag by 10 to 20 seconds. Restarting only DWM cleared the accumulated state and immediately restored smooth window movement.

In a separate control, every ChatGPT.exe and codex.exe process was absent for about 5 minutes 35 seconds. No comparable new growth was observed while Codex was closed, but the handles already held by DWM did not fall. So far, they have only been cleared by restarting DWM or Windows.

I cannot yet tell whether the lifetime problem begins in the Codex UI, Electron, Chromium, DirectComposition, DWM, or the graphics driver. This report documents the repeatable trigger and the resource signature.

Environment

  • Windows 10 22H2, build 19045.7417, x64
  • Codex MSIX OpenAI.Codex_26.707.9981.0_x64
  • Internal app version 26.707.72221, build 5307
  • Electron 42.1.0
  • Chromium/ProductVersion 150.0.7871.115
  • One internal display at 2400 x 1600, 60 Hz; no external display
  • Intel Iris Xe Graphics and NVIDIA GeForce RTX 3050 Ti Laptop GPU
  • HAGS enabled (HwSchMode=2)
  • Variable refresh optimization enabled (VRROptimizeEnable=1)
  • MPO disabled (OverlayTestMode=5)

I have seen the same general problem across several Codex desktop updates. The version above is the one used for the controlled captures below.

Reproduction

  1. Start from a low DWM handle count after signing in to Windows or restarting DWM.
  2. Open Codex and create a new task.
  3. Ask it to make several separate, read-only terminal calls, for example:

``powershell
Get-Date
Get-Location
Get-Process -Id $PID | Select-Object Id, ProcessName, HandleCount
git status --short
Get-ChildItem -Name | Select-Object -First 3
``

  1. Record the count and type of DWM handles before the task, while it runs, and for at least 30 seconds after it finishes.
  2. Repeat the task a few times. It is not necessary to keep Codex in the foreground.
  3. For comparison, create a new task that only replies OK and does not call any tools.

Expected: After a task finishes and its status UI settles, DWM Composition handles should return close to their previous baseline.

Actual: Tasks with several tool calls leave a small persistent net increase. Repeating these task cycles gradually builds a much larger DWM-side handle stock.

Controlled results

| Test | Workload | Persistent DWM handle change | Composition change |
| --- | --- | ---: | ---: |
| Plain text control | New task replied only OK; no tools | +5 | -1 |
| Five terminal calls | Five separate read-only calls | +10 | +22 |
| Idle long task view | Visible and idle for about 10 minutes | +2 | Not sampled by type |
| Idle short task view | Visible and idle for about 10 minutes | 0 | Not sampled by type |
| Active task | Running for about 6 minutes | +66 | Not sampled by type |
| Active task, Codex minimized | Running in the background | +55 | Not sampled by type |
| Scheduled task | Natural unattended run | +88 | Not sampled by type |

The two controlled runs used 30-second medians before and after the task. The DWM PID remained unchanged.

DirectComposition trace

I captured the five-call test with the Microsoft-Windows-DirectComposition and Dwm-Core ETW providers.

During the server-reported task interval, DirectComposition recorded four identical 12-resource creation batches for DWM channel 61. None of those 48 resources had a matching release before the trace ended. An identical fifth batch appeared about 2.1 seconds before the server startedAt timestamp, which may reflect local task initialization, but I cannot prove that from this trace.

Every batch had this resource type sequence:

201, 195, 195, 32, 32, 79, 87, 39, 58, 39, 58, 30

The resource handles were sequential and had openShared=false.

This capture did not include the Dwm-Core channel-attribution keywords, so I cannot map channel 61 to a specific client PID. The same batch signature also appeared occasionally outside the task interval. I therefore treat this as a correlation clue rather than proof that channel 61 belongs to Codex.

The capture used Dwm-Core keyword mask 0x21. A follow-up trace should use 0x900021 so that it also records PROCESS_ATTRIBUTION, CHANNEL_OPENED_FOR_PROCESS, CHANNEL_CLOSED_FOR_PROCESS, and COMMAND_PROCESSED_ONBEHALF. The DirectComposition mask can remain 0x5F.

DWM kept the same PID throughout the run. There was no lock-screen transition. Video.UI emitted no DirectComposition create or release events during the task interval, and the ShellExperienceHost burst began only after completion, consistent with the completion notification.

Related report

The closest existing report I found is #16374, which describes progressive Windows shell freezes associated with Codex. This may be part of the same broader problem. The additional evidence here is the DWM handle type, the no-tool control, and the DirectComposition capture.

Package note

The affected package is an Electron 42 and Chromium 150 application, not WebView2. I found task-status and sidebar lifecycle code in the packaged ASAR, but the production package has no source maps, so I could not map the observed resource lifetime to a specific source module.

Evidence retained locally

The most relevant sanitized results are included above. I have also kept the complete ETL, the two run summaries, before-and-after handle type diffs, and the extracted DirectComposition batches. I have not attached the raw ETL because it contains a wider process timeline.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗