Codex Desktop activity can repeatedly crash macOS WindowServer in IOSurface/ScreenCapture path

Open 💬 1 comment Opened Aug 5, 2026 by yangwuan55

What happened

While using Codex Desktop on macOS, WindowServer crashed repeatedly in the same day. The user saw three WindowServer incidents today; two crash reports were available and the latest report has consecutiveCrashCount: 3.

This appears related to Codex/Computer Use or browser automation interacting with macOS screen/window capture. The crash stack is in the macOS WindowServer/SkyLight/IOSurface/Metal capture path rather than in a normal app process.

What I expected

Codex Desktop should not leave macOS in a state where WindowServer repeatedly aborts. If Codex uses ScreenCaptureKit/window capture/Computer Use/browser screenshots, those capture streams should be scoped to the active session and should not destabilize WindowServer after or between tasks.

Steps to reproduce

The exact minimal repro is not isolated yet, but the observed scenario is:

  1. Run Codex Desktop on macOS with active Codex tasks that use the desktop app/browser/computer-use surfaces.
  2. Continue normal Codex work involving browser/app inspection and screenshot/capture-capable tooling.
  3. Observe WindowServer crashes/restarts multiple times during the day.
  4. Inspect /Library/Logs/DiagnosticReports/WindowServer-*.ips.

Environment

  • macOS 26.5.2 (25F84), arm64
  • Hardware model: Mac17,9
  • GPU: Apple M5 Pro, Metal 4
  • Display at time of current inspection: built-in Liquid Retina XDR only, 3024 x 1964 Retina
  • Codex Desktop app bundle: com.openai.codex, version 26.730.61639
  • Related existing issue: #35659 reports Codex Computer Use leaving a ScreenCaptureKit stream running and causing high WindowServer GPU. This report may be the same class of bug but the symptom is more severe: WindowServer SIGABRT crashes.

Crash evidence

Two WindowServer crash reports from 2026-08-05 were inspected:

WindowServer-2026-08-05-152542.ips
macOS 26.5.2 (25F84), Mac17,9
consecutiveCrashCount: 2
exception: EXC_CRASH (SIGABRT)
termination: Abort trap: 6
fault frames:
__pthread_kill -> pthread_kill -> abort ->
CI::Context::AbortIfUnhandledError ->
-[CIContext _startTaskToRender:toDestination:forPrepareRender:forClear:error:] ->
-[CIContext render:toMTLTexture:commandBuffer:bounds:colorSpace:] ->
CA::OGL::MetalContext::render_ciimage ->
CA::OGL::emit_filter -> CA::OGL::FilterNode::apply
WindowServer-2026-08-05-164638.ips
macOS 26.5.2 (25F84), Mac17,9
consecutiveCrashCount: 3
exception: EXC_CRASH (SIGABRT)
termination: Abort trap: 6
kernel triage: VM - (arg = 0x1) vm_map_copyout_kernel_buffer failed
fault frames:
__pthread_kill -> pthread_kill -> abort ->
WSIOSurfaceDebugTallyAndAbort -> iosurface_create_common ->
WSIOSurfaceCreateTargetableWithFormatAndProtection ->
CaptureSurfaceMetal::CreateMetalBacking ->
CompositorMetal::CreateCaptureSurface ->
WS::Capture::create_iosurface_for_window_list ->
_CGXCaptureContentToIOSurfaces -> _XCaptureContentToIOSurfaces

Both reports also showed com.apple.VirtualDisplayListener plus multiple com.apple.coreanimation.*.external-* threads. That seems relevant because the active display inspection later reported only the built-in display.

A separate WindowServer_2026-08-05-093213_*.cpu_resource.diag also appeared earlier the same day. It attributed samples partly “On Behalf Of: Codex (Service)” and showed heavy WindowServer composition/rendering activity.

Additional context

  • No full .ips files are pasted here to avoid leaking machine/user identifiers, but the stack snippets above are copied from the local crash reports.
  • I did not find a normal app crash for Codex at the same moment; the user-visible failure is macOS WindowServer crashing.
  • There was no obvious panic-full report corresponding to these WindowServer crashes, so this initially looks more like a capture/GPU/WindowServer interaction bug than a physical hardware failure.
  • A useful diagnostic path may be to check whether Codex/Computer Use/browser screenshot capture can leave ScreenCaptureKit, virtual display, or capture surfaces active across task boundaries, and whether capture teardown failures can trigger repeated IOSurface creation failures in WindowServer.

View original on GitHub ↗

1 Comment

yangwuan55 · 22 days ago

Update from August 6 — fresh crash on 26.6 + independent cua_node dyld loop

Updating this issue with a same-day reproduction on macOS 26.6 (25G72) and a second failure mode that may be related.

Hardware / Software snapshot (today)

  • MacBook Pro M5 Pro (Mac17,9), Metal 4
  • macOS 26.6 (Build 25G72), arm64, xnu 12377.161.13~4
  • Codex Desktop app bundle com.openai.codex, version 26.730.61639
  • Bundled Codex CLI 0.146.0-alpha.9.2 (matches the profile @galligan identified in #35659)
  • Upgraded from 26.5.2 today, 09:06:07 CST; first clean boot at 09:09:38 CST

Same path, third incident now on 26.6

WindowServer-2026-08-06-120805.ips — created 2026-08-06 12:08:05 CST, 50 minutes after a prior identical crash on the same install. Thread 0 is byte-identical to the 16:46 evidence already attached to this issue:

__pthread_kill -> pthread_kill -> abort ->
WSIOSurfaceDebugTallyAndAbort(int, int) ->
iosurface_create_common(__CFDictionary const*, char const*, bool) ->
WSIOSurfaceCreateTargetableWithFormatAndProtection ->
CaptureSurfaceMetal::CreateMetalBacking(bool) ->
CompositorMetal::CreateCaptureSurface(CGRect, unsigned int, unsigned int,
                                      WSPixelFormat, float, bool,
                                      CGColorSpace*, unsigned int,
                                      unsigned long long) ->
WS::Capture::create_iosurface_for_window_list(
    WS::Capture::WindowCaptureConfiguration,
    WS::Capture::SurfacePool*, double*, CGRect*, double*,
    WSCaptureStream*) ->
WSCaptureCreateIOSurfaceMachPortForWindowList

Kernel, in the minutes before the abort (verbatim from log show):

process WindowServer[…]: IOSurface count of 65024 approaching limit of 65536
process WindowServer[…]: IOSurface count of 65024 approaching limit of 65536

Both 65024 messages are post-upgrade and pre-crash; same threshold the user saw on 26.5.2.

Live state right now (WindowServer up 3:35 min, currently overstressed)

  • WindowServer PID 34312, 10.8% CPU; at 12:10:45 the kernel reports: caught waking the CPU 45001 times over ~167 seconds, averaging 268 wakes / second and violating a limit of 45000 wakes over 300 seconds. It is back to running but taxed far above normal.
  • Live processes at crash time (PID set stable across multiple minutes):
  • codex 35276 (3m old) + codex 35460 (2m old — Codex restarted after the previous crash and immediately re-attached the same children)
  • cua_node 35319, 35342, 35520, 35536 — four Computer Use children
  • replayd 34529
  • duetexpertd 34681 — repeatedly failing IOSurfaceSharedEventAddEventListener on this install
  • Chrome Helper renderers: 9 (controlled). Not the leak driver in this install.

Independent failure mode — cua_node dyld bootstrap crash loop

Twenty-one node-*.ips files in ~/Library/Logs/DiagnosticReports/ from 11:05 to 12:09 CST. All coalesce to a single incident — same slice_uuid across every report:

slice_uuid: 87fbc746-7d47-3fd8-b0a3-97018cbf954b
bug_type:   309   (DYLD image-loading abort)
exception:  EXC_CRASH / SIGABRT
uptime_ms:  7200  (7.2 s every time)

Stack on every report:

__abort_with_payload -> abort_with_payload_wrapper_internal -> abort_with_payload ->
dyld4::halt(char const*, dyld4::StructuredError const*) ->
dyld4::prepare(dyld4::APIs&, mach_o::UnsafeHeader const*) ->
dyld4::start(dyld4::KernelArgs*, void*, void*, unsigned long long)::$_1::operator()() const ->
start

usedImages resolve to /opt/homebrew/*/... for every image (node, libnode.147.dylib, libuv.1.0.0.dylib, libllhttp.9.4.3.dylib, etc.) even though the actual binary lives at /Applications/ChatGPT.app/Contents/Resources/cua_node/bin/node_repl. dyld emits a wildcard path when its cache pointer fails to bind @rpath to a real on-disk location, then rejects linkage at dyld4::prepare. Every cua_node survives ~7.2 s and dies on SIGABRT.

coalitionName: ai.lark-channel-bridge.bot.codex — confirming these cua_nodes were spawned from a Codex session.

I do not know whether Symptom B is caused by Symptom A or independent. Two plausible mechanisms:

  1. WindowServer aborts mid-ScreenCaptureKit session → next cua_node cannot establish a transient display attachment within dyld's prepare grace → dyld rejects and aborts.
  2. ChatGPT.app's cua_node ships an LC_LOAD_DYLIB that points at a path that does not exist inside the bundle's rootless container.

Both can be true simultaneously; distinguishing them needs controlled isolation I cannot run today.

Workaround that holds

Quitting Codex is not sufficient (matches the existing workaround in the OP). What does hold:

killall codex
pkill -9 -f cua_node
pkill -9 -f SkyComputer
sudo killall -KILL replayd duetexpertd

After that, WindowServer CPU drops, IOSurface count returns to baseline (verified via sysctl kern.ipc.iog_global_state_count family / per-process footprint), and no further aborts.

Frequency

Six confirmed WindowServer aborts across 3 days on this machine, all on the same WSCaptureCreateIOSurfaceMachPortForWindowList call path. With Codex Computer Use enabled, this is now a less-than-2-hour reproducer on current shipping macOS — not a corner case.

What would help, in priority order

  1. Hard guarantee that each SCStream is torn down whenever no consumer is attached. @galligan's #35659 already names the path: the registered-thread set is never cleared on idle, so the supervisor keeps respawning per-thread clients even after the model turned off.
  2. Tie event-stream mcp client lifetime to a single turn, not to a registered thread that may never be deregistered.
  3. For Symptom B: ship ChatGPT.app/Contents/Frameworks/libnode.*.dylib and have LC_LOAD_DYLIB use @executable_path/../Frameworks/libnode.*.dylib, not a Homebrew path that does not exist inside the bundle's rootless container.
  4. Add a Codex-side watchdog that detects its own leak rate (e.g. rising WindowServer CPU or live SkyComputerUseClient event-stream mcp count) and refuses to spawn a new Computer Use turn past a red line.

Attachments available on request

  • WindowServer-2026-08-06-120805.ips (63,879 B) — captured at 12:08:05, Thread 0 above is verbatim from this file.
  • 21× node-2026-08-06-1*.ips from 11:05 to 12:09 CST, all coalescing to one logical incident.
  • 5-second sample of WindowServer taken at 12:08:25 (post-restart, while overstressed).
  • vmmap summary across all four cua_node children at the same instant.
  • A stripped sysdiagnose archive (~600 MB, will need a 1-GB upload location).

The whole session stash lives at ~/Documents/diagnostics/windowserver-2026-08-06/. Happy to attach any subset. /cc @openai/codex maintainers — this is the same class as #35659 / #29157 / #26293 and is now reproducible within hours on shipping macOS.