Skip to content

fix(desktop): preview snapshots no longer stall on Chromium's legacy capture path - #32415

Closed
juliusmarminge wants to merge 5 commits into
mainfrom
fix/issue-32410
Closed

juliusmarminge wants to merge 5 commits into
mainfrom
fix/issue-32410

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Problem

Desktop Browser snapshots intermittently fail with Preview automation snapshot timed out after 15000ms and can keep failing until the tab is replaced. The desktop browser host keeps a preview tab offscreen and makes it paintable only while Page.captureScreenshot is pending; on that path Chromium's legacy CDP capture can stop answering. Playwright turns on CDPScreenshotNewSurface whenever it launches Chromium or Electron itself, but T3 launches Electron on its own and attaches over CDP, so the desktop app never gets it.

Reproduced on main (83a3674) on Linux x86_64 under Xvfb with the reporter's native probe, which wires a real Electron <webview> through T3's createCdpRelayConnection and calls the real ServerBrowserPage.snapshot 100 times: every run stalled within the first 5 snapshots (with --disable-gpu and without) and failed with The page was too busy to capture a snapshot in time… after 10 s.

Change

Before Electron is ready, desktop startup appends --enable-features=CDPScreenshotNewSurface on every platform. The value is merged into any existing --enable-features list, because Chromium keeps only the last value of a features switch (the same reason the Linux --disable-features merge exists). Nothing is added when the feature is already enabled or when the user turned it off with --disable-features.

Scope and approval

Fixes #32410

Verification

  • vp test run apps/desktop/src/app/DesktopPreReadyPlatform.test.ts apps/desktop/src/app/DesktopEarlyElectronStartup.test.ts: 29 passed. The new tests fail on main: on darwin, win32 and linux startup leaves no feature (expected [] to have the same members as [ 'CDPScreenshotNewSurface' ]), and an existing --enable-features list is not extended. A third test covers the --disable-features opt-out.
  • vp run check:changed: format, lint, knip, typecheck and tests pass.
  • Native probe, Electron 44.10.1 under Xvfb, --no-sandbox, startup through the real DesktopPreReadyPlatform.make of each side:
Side Startup enable-features Runs Result
Before (main 83a3674) (none) 3 stalled at snapshot 4, 3, 3; each failed after ~10 s with the busy-page error, with 3.6–3.8 s captures before it
After (79958c1) CDPScreenshotNewSurface 3 100/100 snapshots each, current pixels and accessibility text verified, median 63–64 ms, max 155 ms
After, launched with --enable-features=WebRTCPipeWireCapturer WebRTCPipeWireCapturer,CDPScreenshotNewSurface 1 100/100, max 139 ms

Driver: the reporter's probe gist (build.mjs, host.html, reproduce.cjs), run unchanged with PROBE_PRODUCTION=1.

Before transcript, run 1 (main)
{"event":"startup","features":""}
{"event":"snapshot","iteration":0,"feature":false,"ms":121,"size":[500,300],"rgb":[255,0,0],"tree":true}
{"event":"snapshot","iteration":1,"feature":false,"ms":84,"size":[500,300],"rgb":[0,0,255],"tree":true}
{"event":"snapshot","iteration":2,"feature":false,"ms":3594,"size":[500,300],"rgb":[255,0,0],"tree":true}
{"event":"snapshot","iteration":3,"feature":false,"ms":82,"size":[500,300],"rgb":[0,0,255],"tree":true}
{"event":"failure","iteration":4,"feature":false,"ms":10010,"message":"The page was too busy to capture a snapshot in time. Wait for it to settle and take another snapshot."}

Independent run on a physical Linux x64 GNOME/Wayland desktop (GPU and sandbox on), reported in this PR's comments: with no feature enabled, the same probe stalled at snapshot 12 after 10,011 ms with the busy-page error; with CDPScreenshotNewSurface enabled through DesktopPreReadyPlatform.make, 100/100 passed. That after-run used the equivalent change in #32412, not this head.

Startup switches in a real Electron process

Ubuntu 24.04 x64, Electron 44.10.1, Xvfb 1920×1080×24 with Openbox. 44 Electron processes ran the real DesktopPreReadyPlatform.make of each side before ready; all assertions pass. C=CDPScreenshotNewSurface, W=WaylandWpColorManagerV1, E/D=enable/disable list, ∅=absent. L rows use a stubbed hyprctl to reach the Linux Hyprland merge; they prove the two merges compose, not compositor behaviour.

Row Starting switches E / D Before
80e62619f5
After
1b38b8ce40
Expected after
A0 ∅ / ∅; two calls ∅ / ∅ C / ∅ Append C once
A1 Empty, bare or whitespace E or D; six variants Native empty values/presence retained E=C; D preserved Normalize reads; preserve D presence
A2 WebRTCPipeWireCapturer,UseOzonePlatform / ∅; two calls E unchanged Existing E,C / ∅ Append once
A3 Decorated unrelated entry, CExtra, lowercase C; outer spaces E unchanged Outer-trimmed E,C / ∅ Exact-name match; retain parameters
A4 C; Other,C; Other,*C with study/parameters; two calls each Unchanged Unchanged No enable append
A5 ∅ / C Unchanged Unchanged Honor opt-out
A6 Other with study/parameters / Vulkan,C bare or decorated Unchanged Unchanged Preserve both lists; no append
A7 Other,C / Vulkan,C Unchanged Unchanged No append
A8 ∅ then Other / Vulkan Unchanged C then Other,C / Vulkan Preserve unrelated disables
L1 ∅ / ∅ ∅ / W C / W Both helpers compose
L2 Other / Vulkan Other / Vulkan,W Other,C / Vulkan,W Preserve both lists
L3 Other / Vulkan,C Other / Vulkan,C,W Same Preserve opt-out
L4 Other,C / Vulkan Other,C / Vulkan,W Same No duplicate C

Full inputs and results: startup-table.md, startup-before.ndjson, startup-after.ndjson.

Integrated desktop app

Real desktop app of each side under Xvfb (--no-sandbox --disable-gpu), fresh isolated T3 home, T3CODE_DEV_SEED=0, snapshots driven through the production preview MCP tools on a synthetic page.

Runtime row Before
80e62619f5
After
1b38b8ce40
Observed
R3 shown → hidden → shown → reload 28/28 fresh; median/max 49/77 ms 28/28 fresh; 64/110 ms Real desktop webview and IPC; hidden rect=(-100000,-100000), visible=false; reload loadId changes
R4 viewer + recording 5/5 fresh; viewer first JPEG 23 ms; recording seed JPEG 15 ms 5/5 fresh; viewer first JPEG 17 ms; recording seed JPEG 7 ms Viewer still refresh and continued frames; four snapshots during recording; stop succeeds
R5 minimize → restore 3/3 fresh; median/max 47/64 ms 3/3 fresh; 39/43 ms Native minimized=true, capture; restored minimized=false, two captures
R6 native macOS cannot-verify macos cannot-verify macos No native macOS run; not attempted
R7 native Windows cannot-verify windows cannot-verify windows No native Windows run; not attempted

The stall did not reproduce in the integrated app on either side under Xvfb, so these rows show the flag causes no regression across hide/show, reload, recording, the viewer and minimize; the stall fix itself rests on the native probe above. Viewer JPEG refreshes after the first capture took 12–28 ms before and 5–7 ms after.

Recordings: desktop-before.mp4, desktop-after.mp4, desktop-before-after.mp4, preview-before.mp4, preview-after.mp4. Transcripts: runtime-table.md, runtime-before.ndjson, runtime-after.ndjson. Drivers and stubs (not committed): integrated-run.mjs, build-startup.mjs, startup-matrix.mjs, hyprctl, all-drivers-and-stubs.tar.gz.

What else changed: nothing beyond the three files in this diff. Both sides shared driver, page, flags and fresh-state recipe; ports, IDs, timestamps and encoder start-up time vary.

cannot-verify macos, cannot-verify windows: no native macOS or Windows run; those platforms are covered by the unit tests and by Playwright enabling the same feature by default on every platform. A minimized window was checked on Linux/Openbox only.

Verification log
  • Merged main (80e6261) without conflicts; vp run check:changed passes on 1b38b8c (format, lint, knip, typecheck, 29 tests).
  • Review round 1: two independent reviewers, both MERGE, no findings.
  • Proof audit: pass 1 derived a 21-row matrix; pass 2 checked every capture and recording, rebuilt one startup row independently (matching), and found all Linux rows covered; macOS and Windows unverified as above.

AI-assisted; verified with the tests and tools listed above.

…re surface

These tests reproduce #32410: the desktop app launches Electron without
Chromium's CDPScreenshotNewSurface feature, so Page.captureScreenshot on a
webview that is moved offscreen can stall. They assert that pre-ready
startup enables the feature on darwin, win32 and linux, and keeps any
features already passed in --enable-features.
…capture path

Preview snapshots call Page.captureScreenshot on a tab kept offscreen, and
Chromium's legacy capture path can stop answering there. Playwright avoids
this by enabling CDPScreenshotNewSurface when it launches Chromium, but T3
launches Electron itself and attaches over CDP.

Pre-ready startup now enables CDPScreenshotNewSurface on every platform,
merged into any existing --enable-features value, unless the user already
enabled it or turned it off with --disable-features.

Fixes #32410
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Nov 7, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All clear

Posted via Macroscope — Effect Service Conventions

@coderabbitai

coderabbitai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 1b38b8c and 7e6c97c.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

Startup resolves CDPScreenshotNewSurface against existing enable and disable feature values. Pre-ready launch arguments include the resolved enable switch when applicable. Tests cover macOS, Windows, and Linux.

Changes: CDP screenshot feature switch

Layer / File(s) Summary
Feature switch resolution
apps/desktop/src/app/DesktopEarlyElectronStartup.ts
The resolver preserves existing feature entries. It returns null if the feature is already enabled or disabled; otherwise, it adds the feature to the enable value.
Pre-ready launch integration and tests
apps/desktop/src/app/DesktopPreReadyPlatform.ts, apps/desktop/src/app/DesktopPreReadyPlatform.test.ts
Pre-ready startup appends the resolved enable switch when present. Tests cover three platforms, retained enable entries, and an existing disable entry.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

Possibly related PRs

  • pingdotgg/t3code#32412 — Both changes enable CDPScreenshotNewSurface before Electron is ready.
  • pingdotgg/t3code#28991 — It changes pre-ready configuration and boot timing, which affect when this change applies the screenshot switch.
  • pingdotgg/t3code#24133 — Both changes address desktop preview screenshot stalls, using different capture-path changes.

Suggested reviewers: maria-rcks

Merge Risk: Minimal · previous review ab788

No actionable merge-blocking issue was found in the startup switch change.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title describes the desktop snapshot stall addressed by the change.
Linked Issues check Passed [#32410] Pre-ready startup enables CDPScreenshotNewSurface and preserves existing feature flags and explicit opt-outs. The current summary reports cross-platform enablement tests and tests for flag …
Out of Scope Changes check Passed [#32410] The startup changes and tests support the snapshot fix and preserve existing feature settings. No unrelated changes are identified in the whole-PR summary.
Description check Passed The description covers the required Problem, Change, Scope and approval, and Verification sections. It links the related issue and gives detailed test and probe results, including platforms that were …
Full details: Linked Issues check

Explanation

[#32410] Pre-ready startup enables CDPScreenshotNewSurface and preserves existing feature flags and explicit opt-outs. The current summary reports cross-platform enablement tests and tests for flag preservation and opt-out. The author reports a successful native snapshot probe. The incremental diff was unavailable; the current whole-PR evidence supports the assessment.

Full details: Description check

Explanation

The description covers the required Problem, Change, Scope and approval, and Verification sections. It links the related issue and gives detailed test and probe results, including platforms that were not verified. It notes AI assistance but does not name the model and harness as the template requests; this omission does not make the otherwise complete description fail.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 79958c1 and ab78833.

Files selected for processing: 1

  • apps/desktop/src/app/DesktopPreReadyPlatform.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@macroscopeapp

macroscopeapp Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — Desktop startup now enables Chromium's newer screenshot capture path by default to prevent preview snapshot stalls, while respecting explicit disables. Adopting this product-default change requires human review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 7e6c97c. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@shivamhwp

Copy link
Copy Markdown
Collaborator

[GPT-6.1 Sol][Codex] on behalf of shivam.

Independent #32410 findings from corex's physical Linux x64 desktop, GNOME/Wayland, Electron 44.10.1, Playwright Core 1.60.0. Normal GPU rendering and sandbox remained enabled. The native probe used an embedded webview, the real createCdpRelayConnection and ServerBrowserPage.snapshot, with separate profile/state directories for each side. Its window used showInactive().

This tested the earlier fix in #32412 at 4fa36c54ae2e0d0e30db7983d4da716cfb358e3f, based on 83a3674ffd84ffb33de768210c806cb1ac53fd80, not this PR's head. With production startup bypassed and no enabled feature, 12 captures succeeded, then iteration 12 stalled for 10,011 ms and returned the busy-page error. Executing that fix's real DesktopPreReadyPlatform.make enabled CDPScreenshotNewSurface; all 100 captures passed checks for changing PNG pixels and current accessibility labels. I inspected the final blue image with button text Frame 99.

Retained transcript excerpts:

{"event":"failure","iteration":12,"feature":false,"ms":10011,"message":"The page was too busy to capture a snapshot in time. Wait for it to settle and take another snapshot."}
{"event":"startup","features":"CDPScreenshotNewSurface"}
{"event":"snapshot","iteration":99,"feature":false,"ms":45,"size":[500,300],"rgb":[0,0,255],"tree":true}

The per-capture feature:false field is the unused manual override, not the effective feature setting. vp test run apps/desktop/src/app/DesktopPreReadyPlatform.test.ts also passed all seven tests at the earlier head. Fresh Codex review found no blocking code findings there.

Limits: the probe bypasses DesktopBrowserHost and renderer IPC, so this is native capture-path evidence rather than a full packaged-app pass. Minimized windows and physical macOS/Windows were not tested. No tests were run against #32415. Test processes exited and temporary artifacts were cleaned up. Handing this job over to you; no duplicate PR or further changes.

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed labels Nov 7, 2026
Wladefant

This comment was marked as spam.

@juliusmarminge juliusmarminge added pr:gate-failed A land gate failed and removed pr:in-progress A lead is working on this PR labels Nov 10, 2026
Comment on lines +295 to +298
assert.deepEqual(
appendSwitchMock.mock.calls.map(([name]) => name),
["enable-features"],
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mock-call assertion repeats the feature-switch test and makes the unrelated startup-order test depend on the exact switch list. Could it be removed?

-        assert.deepEqual(
-          appendSwitchMock.mock.calls.map(([name]) => name),
-          ["enable-features"],
-        );

Posted via Macroscope — Test Signal

Copy link
Copy Markdown
Member Author

The automated pipeline stopped on this pull request and will not merge it:

the capture step could not show the problem on the base commit, so there is nothing to prove fixed.

On each build, opened the same synthetic changing page. Repeated 120 cycles: hide the right panel, close the floating preview, wait 250 ms, update the numbered page revision, request a screenshot and accessibility snapshot while hidden, show the right panel, wait 250 ms, request another snapshot. Both builds returned 240 fresh snapshots without timeout or busy-page errors. Corresponding screenshot hashes matched across builds. Recorded each complete sequence. Final stills came from identical screenshot pairs 500 ms apart. Uploaded evidence, stopped all launched processes, and removed both worktrees and scratch files.

It stays open for a person to finish or close.

@juliusmarminge

Copy link
Copy Markdown
Member Author

Thanks for this. #32412 makes the same change and is the one being kept.

#32412 enables the same screenshot surface and also handles the post-navigation “Unable to capture screenshot” failure reported after enabling it. That additional fix makes it the more complete change to carry forward.

Your feature-list handling and explicit opt-out coverage are useful refinements to preserve when consolidating the fixes.

Closing as a duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed size:S 10-29 changed lines (additions + deletions). vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop preview snapshots stall on Chromium’s legacy capture path

3 participants