chat-open click frames: ScreenCaptureKit when the step has no Screen Recording grant, so the chat page is in them

Merged
TE
3d ago
+458-11

Summary

The chat-open click harness (scripts/measure/chat-open.sh, chat-open-bench.py click cases) times a sidebar click to the chat's final frame from debug.new_tab frame_strip pixels. On the fleet measure workers every frame after the click showed the chat pane empty, even for a kept page whose layout watch reported rows drawn, so the cold-click number timed the native pane switch, not chat text reaching the screen.

Root cause (measured on a measure worker with chat-open-bench.py --capture-check):

  • The step's process, and the app the bench launches as its child, answer to the build worker as their responsible process. That binary is ad-hoc signed and holds no Screen Recording grant (CGPreflightScreenCaptureAccess() is false), and SIP is on, so no agent path can grant one, and a person's grant would not survive the next worker update, since the binary's hash changes.
  • Without the grant, CGWindowListCreateImage (frame_strip's capture) leaves out what Chromium's GPU process draws: the page window comes out as its background. (A WebKit page in a probe app on the same worker was captured fine; the redaction hits Chromium's out-of-process layers.)
  • ScreenCaptureKit with the current process's shareable content needs no grant and has the page. A same-step comparison showed the transcript through ScreenCaptureKit and an empty pane in the frame_strip frames.

Fix:

  • FrameStripRecording (CmuxNextDesign, DEBUG and bench builds only): with above and no Screen Recording grant, frame_strip reads ScreenCaptureKit's current-process content: the display showing only this app's windows, cut to the window's rect, every composited frame (an idle frame repeats the last picture). With the grant, with capture: "window_list", or when ScreenCaptureKit fails, it uses the window list as before. The report says which: capture, capture_error.
  • The key waits for the first ScreenCaptureKit frame as well as the 120 ms lead, and the recording ends that much later, so duration_ms after the key stays recorded. The reply's lead_ms says when the key went in. The bench uses it for the click time.
  • The --capture-check bench flag (screen_capture_check.py) reports whether the step may read the screen: the preflight, the responsible process and its signature, csrutil, screencapture, and the CuaDriver daemon's capture.
  • DebugFrameStrip shrinks by 2 lines. The new queue debug.frame_strip.screen is listed in plans/cmux-next/threading-model.md.

Timers and cleanup: the lead, first-frame and end waits are one-shot DemandTimers, each cancelled or fired once. The stream stops at the end of the strip, and nothing outlives the request.

Testing

  • App compile check at this head: passed.
  • prepush-gate.py over the branch: no new violations.
  • Fleet proof: an exact-head chat-open.sh --cases=click,click_again run on a measure worker whose kept PNGs show transcript text (results on the owning issue).

Observed / Not run

  • Observed: capture-check steps showed preflight false and the ad-hoc worker as the responsible process. A probe app's WKWebView page was captured by the window list and by ScreenCaptureKit. ScreenCaptureKit's picture of the real chat showed the transcript while the frame_strip frames were blank.
  • Not run: a unit test. The capture needs a console session and a Chromium page. The fleet run is the regression check, and its JSON records capture per open.

Changelog

none

Agent session: agent session

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the chat-open click benchmark on fleet measure workers, where every frame after the click showed the chat pane empty because the worker's process holds no Screen Recording grant and the window list's captures left out what Chromium's GPU process drew.

  • FrameStripRecording reads ScreenCaptureKit's current-process content instead when above is set and no grant exists (no grant needed, Chromium pages included), falling back to the window list with the grant or on failure; the report says which via capture/capture_error.
  • The lead wait now also waits for ScreenCaptureKit's first frame, and the bench corrects the click time from the reply's lead_ms.
  • New --capture-check flag (screen_capture_check.py) reports whether the step may read the screen: preflight, responsible process and signature, csrutil, screencapture, and the CuaDriver daemon's capture.