Windows 10 Computer Use deadlocks awaiting SoftwareBitmap conversion inside FrameArrived

Open 💬 0 comments Opened Jul 26, 2026 by zbw202301-droid

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

26.721.4979.0

What subscription do you have?

ChatGPT Plus

What platform is your computer?

Microsoft Windows NT 10.0.19045.0 x64

What issue are you seeing?

On Windows 10 22H2 build 19045, Computer Use screenshot capture still times out after the unsupported IsBorderRequired call is skipped.

Windows.Graphics.Capture starts successfully and delivers a real FrameArrived event. TryGetNextFrame returns a valid Direct3D11CaptureFrame and IDirect3DSurface. The helper then synchronously waits for SoftwareBitmap.CreateCopyFromSurfaceAsync inside the CreateFreeThreaded FrameArrived callback.

The async operation accepts its completion handler with HRESULT 0x00000000, but the completion handler is never invoked while FrameArrived remains blocked. The outer request eventually reports:

FrameArrived timed out: timed out waiting on channel

This is downstream of the Windows 10 IsBorderRequired compatibility problem reported in #25178. Ignoring E_NOINTERFACE exposes this second independent failure.

What steps can reproduce the bug?

  1. Run Codex Desktop 26.721.4979.0 with Computer Use plugin 26.721.41059 on Windows 10 22H2 build 19045.
  2. Start Computer Use and request a screenshot of a normal window such as Notepad.
  3. Skip E_NOINTERFACE (0x80004002) from GraphicsCaptureSession.IsBorderRequired so initialization continues.
  4. CreateFreeThreaded, event registration, session creation, and StartCapture succeed.
  5. FrameArrived enters on a WGC worker thread.
  6. TryGetNextFrame succeeds and Surface is valid.
  7. CreateCopyFromSurfaceAsync returns an operation and put_Completed returns S_OK.
  8. The completion handler is never invoked while FrameArrived synchronously waits.
  9. The screenshot request times out waiting for its result channel.

A standalone WGC probe confirmed that both CreateFreeThreaded and Create + DispatcherQueue continuously produce valid frames on this machine. The stall reproduced only when CreateCopyFromSurfaceAsync(...).get() ran synchronously inside FrameArrived.

It reproduced with both a Rust/windows-rs-style agile COM delegate and a standard C++/WinRT delegate, excluding the delegate ABI as the cause.

What is the expected behavior?

FrameArrived should return without synchronously waiting for SoftwareBitmap conversion.

The helper should retain the frame or surface, return from the callback, and perform SoftwareBitmap conversion and JPEG encoding on a dedicated MTA worker before notifying the existing result channel.

The validated probe used this structure and conversion completed successfully in about 12.9 ms while WGC continued producing valid frames.

This does not require switching to DispatcherQueue or replacing Windows.Graphics.Capture.

Additional information

Environment:

  • Windows 10 22H2, build 19045
  • Codex Desktop: 26.721.4979.0
  • Computer Use plugin: 26.721.41059
  • @oai/sky: 0.5.2
  • Native helper: x64 Rust/MSVC codex-computer-use.exe
  • Rust Windows bindings: windows 0.61.3, windows-core 0.61.2
  • GPU: AMD Radeon RX 6650 XT

Dynamic evidence for this helper build:

FrameArrived handler Invoke:        RVA 0xC17FC
valid Surface path:                 RVA 0xC1993
CreateCopy async operation created: RVA 0xC1D69
async completion registration:      RVA 0xC1F19
put_Completed call:                 RVA 0xC1F59
put_Completed return:               RVA 0xC1F5C
async completion handler Invoke:    RVA 0x1268BC
conversion result path:             RVA 0xC2053
result/channel notification:        RVA 0xC4CC0

Observed: put_Completed returns S_OK, but the completion handler, conversion result path, and result-channel notification are never reached.

Validated worker result:

WGC callback thread:     11656
conversion worker:       20544
conversion completed:    true
conversion latency:      12.865 ms
FrameArrived count:      240
valid frame count:       240

Suggested fix:

  1. Call TryGetNextFrame in the callback.
  2. Move Direct3D11CaptureFrame to a bounded worker queue.
  3. Return from FrameArrived immediately.
  4. On a dedicated MTA worker, obtain Surface, await CreateCopyFromSurfaceAsync, encode JPEG, and notify the existing channel.
  5. Preserve the current one-result guard, timeout, cancellation, and late-result handling.

View original on GitHub ↗