Claude Code Desktop: WindowServer at ~47% CPU while a response streams (deep CoreAnimation layer tree re-walked at 120 Hz)
Summary
While the Claude desktop app generates a response, macOS WindowServer runs at about 47% of one CPU core. The app window is otherwise cheap: with the app idle and the same window visible, WindowServer sits at 3-6%.
A stack sample of WindowServer shows the time goes into re-walking the Claude window's CoreAnimation layer tree, which nests about 50 levels deep, once per display refresh (120 Hz on this machine).
Environment
- Claude desktop 1.52386.3
- macOS 26.6.2 (25G83)
- MacBook Pro, Apple M3 Pro, 14-core GPU
- Built-in Liquid Retina XDR display, 1512x982 points @ 3024x1964, 120 Hz
Reproduce
- Open the Claude desktop app with one window visible and nothing else on the space.
- Record
WindowServerCPU every 2 seconds. - Send a prompt that produces a long reply.
- Compare the CPU while the reply streams against the CPU when the app is idle.
Measurement
One continuous 2-minute trace. Nothing else changed. No other app window was on screen.
2s 48.5% previous reply still streaming
6s 34.0%
8s 8.0% streaming stops
12s 3.0%
...
54s 3.0% idle
58s 3.0%
60s 5.0%
62s 37.0% new reply starts streaming
64s 50.0%
73s 50.5%
87s 48.0%
101s 48.0%
105s 27.5% reply ends
111s 2.5%
121s 3.0% idle
Idle: 3-6%. Streaming: 41-51%.
Stack sample
sudo sample WindowServer 10. All other WindowServer threads are idle; the render-server thread is blocked in mach_msg for 6440 of 6472 samples. All the time is on the main thread:
CGXRunOneServicesPass
post_port_data
non_coalesced_timer_handler
run_timer_pass
update_display_callback(void*, double) 2545
CGXUpdateDisplay 2544
WS::Updater::UpdateDisplays 2050
WS::Updater::prepare_coreanimation 1893
ca_prepare_begin_window_update(CGXWindow*) 1657
WSSessionConnectionPerformWithPluginOwner 1654
CARenderUpdateAddContext2 1487
CA::Render::Update::add_context 802
CA::Render::Updater::prepare_layer0 \
CA::Render::Updater::prepare_sublayer0 / ~50 levels of recursion
CA::Render::Updater::prepare_portal
One window accounts for this. The ca_prepare_begin_window_update branch with 1657 samples is the Claude window. Every other ca_prepare_begin_window_update branch in the sample has 20 samples or fewer.
Attribution
- Hiding the Claude window (Command+H) during streaming takes
WindowServerfrom 47% to 1.5%. Restoring it returns to 47%. - Hiding every other application on the space changes nothing. Nine applications were eliminated one group at a time.
- A second Electron application was on screen for one round and contributed nothing measurable. This is specific to the Claude window.
- A shorter conversation does not help. A fresh session with a two-message transcript measured 51.2%.
- A smaller window costs less, consistent with fewer layers in the tree.
Ruled out
Scaled display resolution (tested at native 1512x982 @ 3024x1964), video playback in another app, HID event rate (2.3 events/s), accessibility zoom and color filters (all off), EDR (headroom 1.0, inactive), Stage Manager (off), external displays (none), GPU saturation (10% device utilization), WindowServer log errors (none in 20 minutes).
Expected
A streaming text animation should not require WindowServer to re-walk a 50-level layer tree at 120 Hz. Flattening the animated region into fewer composited layers, or confining the animation to its own layer, should remove most of this cost.