Skip to content

Keep blocking browser automation off main - #6696

Merged
lawrencecchen merged 9 commits into
mainfrom
feat-browser-eval-hang-audit
Jun 23, 2026
Merged

lawrencecchen merged 9 commits into
mainfrom
feat-browser-eval-hang-audit

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Move browser automation commands that wait on page JS, WebKit cookies, or screenshot callbacks onto the socket-worker lane.
  • Keep WebKit/UI access on main with explicit hops while the command wait happens off-main.
  • Extend the execution-policy regression test for screenshot, frame select, dialog, cookies, storage, console/errors, state, and script/style injection commands.

Verification

  • swift test --package-path Packages/macOS/CmuxControlSocket --filter ControlCommandExecutionPolicyTests
  • ./scripts/reload-cloud.sh --tag behang, run https://github.com/manaflow-ai/cmux/actions/runs/28024332659
  • Tagged preflight through /tmp/cmux-debug-behang.sock: eval, storage get/clear, frame select, cookies set/get/clear, console list, errors list, state save/load, addscript, addinitscript, addstyle, screenshot. Dialog dismiss returned expected not_found without hanging.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Touches many browser control-socket code paths and main-thread synchronization; incorrect routing could hang the UI or regress automation, though policy tests and the established worker-lane pattern reduce that risk.

Overview
Moves blocking browser socket automation off the main actor so WebKit/JavaScript waits no longer freeze SwiftUI.

Previously, commands such as screenshot, frame select, dialogs, cookies, storage, console/errors, state save/load, and script/style injection ran on the main processV2Command path. They are now listed in ControlCommandExecutionPolicy.socketWorkerMethods, dispatched from the socket worker via v2BrowserAutomationCommandOnSocketWorker (renamed from the JS-only router), and removed from the main-actor browser switch.

Handlers are refactored to nonisolated with v2BrowserWithPanelContext: blocking work stays on the worker thread while v2MainSync covers WebKit/AppKit access, cookie-store callbacks, screenshot capture, and browser state dictionary updates. Response assembly is centralized with v2BrowserPanelFields.

Adds the browser-automation-webkit-waits-off-main review rule and a CodeRabbit/Greptile pre-merge check, plus ControlCommandExecutionPolicyTests coverage so the moved commands stay worker-routed (lightweight commands like browser.get.title and browser.frame.main remain on main).

Reviewed by Cursor Bugbot for commit a4209bc. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Moves blocking browser automation off the main actor so the UI doesn’t hang. Routes all page JS, WebKit cookie, hook, and screenshot waits through the socket worker, and trims response payload boilerplate.

  • Refactors

    • Route navigate/back/forward/reload, eval/wait/snapshot/screenshot, input/find/highlight, frame select, dialog, cookies, storage, console/errors, state save/load, and script/style injection through v2BrowserAutomationCommandOnSocketWorker.
    • Keep WebKit work on main via v2MainSync; run WKHTTPCookieStore get/set/delete, screenshot capture callbacks, and telemetry/dialog hook source fetches on main. Mark blocking helpers nonisolated and use v2BrowserWithPanelContext to resolve panel/webView safely.
    • Trim response assembly with v2BrowserPanelFields to unify workspace/surface fields.
    • Add the browser-automation-webkit-waits-off-main review rule and enforce the “cmux browser automation off-main” check in CodeRabbit/Greptile.
  • Tests

    • Extend ControlCommandExecutionPolicyTests to assert worker routing for the moved commands; keep browser.get.title and browser.frame.main on main.

Written for commit a4209bc. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Improvements

    • Refined browser automation command handling for screenshots, dialogs, cookies, storage, and frame selection.
  • Chores

    • Updated code review rules and infrastructure standards for browser automation processes.
    • Enhanced test coverage for command execution policies.

@vercel

vercel Bot commented Jun 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jun 23, 2026 5:28pm
cmux-staging Building Building Preview, Comment Jun 23, 2026 5:28pm

@coderabbitai

coderabbitai Bot commented Jun 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Browser automation commands that wait on WebKit/page callbacks (screenshot, frame select, dialog respond, cookies, storage, console, errors, state save/load, script/style injection) are moved to the socket-worker dispatch lane via ControlCommandExecutionPolicy.socketWorkerMethods. Handlers in TerminalController.swift are refactored to use v2BrowserWithPanelContext, new nonisolated cookie-store helpers, and v2MainSync for WebKit/AppKit access. Enforcement rules are added to CodeRabbit, Greptile, and the review-bot docs.

Changes

Browser Automation Off-Main Routing

Layer / File(s) Summary
Review and enforcement rule definitions
.coderabbit.yaml, .github/review-bot-rules/browser-automation-webkit-waits-off-main.md, .github/review-bot-rules/README.md, .greptile/config.json, .greptile/files.json, .greptile/rules.md
Defines the browser-automation-webkit-waits-off-main pass/fail rule document, registers it in CodeRabbit path instructions and a new pre-merge error-mode check, and adds it to Greptile config, file scope, and rules checklist.
ControlCommandExecutionPolicy: worker method classification and tests
Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift, Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandExecutionPolicyTests.swift
Adds screenshot, frame selection, dialog, cookie, storage, console, errors, state, and script/style injection commands to socketWorkerMethods; updates comment to document v2MainSync pattern; tests verify the enlarged worker set and update main-actor vs worker classification.
Socket-worker dispatcher and main-switch adjustments
Sources/TerminalController.swift
Expands the socket-worker browser automation allow-list, routes browser.highlight through the automation router, and updates processV2Command main-switch comments and case ordering to distinguish waiting automation commands from non-waiting JS-eval commands.
Screenshot, frame select, and dialog respond refactors
Sources/TerminalController.swift
Refactors v2BrowserScreenshot to call captureAutomationVisibleViewportSnapshot inside v2MainSync; refactors v2BrowserFrameSelect and v2BrowserDialogRespond to use v2BrowserWithPanelContext; introduces v2BrowserEnsureTelemetryHooks(webView:), v2BrowserEnsureDialogHooks(webView:), and v2PNGData(from:) helpers.
Cookie-store nonisolated helpers and cookie endpoint refactors
Sources/TerminalController.swift
Marks v2BrowserCookieDict as nonisolated; introduces nonisolated WKHTTPCookieStore wrappers (getAllCookies, setCookie, delete); refactors v2BrowserCookiesGet/Set/Clear to use v2BrowserWithPanelContext and the new helpers.
Storage, console, and errors endpoint refactors
Sources/TerminalController.swift
Refactors v2BrowserStorageGet/Set/Clear, v2BrowserConsoleList, and v2BrowserErrorsList to use v2BrowserWithPanelContext and execute JS against ctx.webView; response payloads updated to ctx-scoped workspace/surface identifiers.
State save/load and script/style injection refactors
Sources/TerminalController.swift
Refactors v2BrowserStateSave and v2BrowserStateLoad to persist/restore frame_selector and cookies via context helpers; refactors v2BrowserAddInitScript, v2BrowserAddScript, and v2BrowserAddStyle to operate against ctx.webView.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#5424: Refactors BrowserPanel.captureAutomationVisibleViewportSnapshot() — the same method now invoked via v2MainSync inside v2BrowserScreenshot in this PR.
  • manaflow-ai/cmux#5778: Expands ControlCommandExecutionPolicy.socketWorkerMethods and TerminalController socket routing for browser commands, the same classification surface extended here.
  • manaflow-ai/cmux#6345: Modifies v2BrowserStorageGet/Set/Clear in TerminalController.swift, the same storage command surface refactored in this PR.

Poem

🐇 Hops off the main thread, light as a breeze,
WebKit callbacks now handled with ease.
The socket worker carries the wait,
No more blocking — the main stays straight!
Cookies, frames, and screenshots too,
All routed right, the rabbit approves you. 🍪


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (4 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error PR introduces DispatchQueue.main.sync, DispatchSemaphore, NSLock, and CFRunLoopRun in socket-worker browser paths, violating swift-blocking-runtime.md rules for latency-sensitive socket code. Use actor-based coordination or real signals (callbacks, async sequences) instead of manual DispatchSemaphore/NSLock waits; replace DispatchQueue.main.sync with proper async bridges or pre-resolved references to avoid blocking socket thr...
Cmux Swift @Concurrent ❌ Error CPU-heavy v2PNGData image encoding is called inside v2MainSync in v2BrowserScreenshot, blocking the main thread during PNG conversion instead of deferring it to the socket-worker lane after c... Move PNG encoding after v2AwaitCallback returns: capture returns NSImage to socket worker, then encode to PNG data outside v2MainSync per the review comment guidance.
Cmux Swift Logging ❌ Error Sources/TerminalController.swift:8944 exposes raw cookie payload in error response, violating swift-logging.md rule against logging credentials/personal data without redaction. Return only safe cookie fields (name, domain, path) in error data, omitting the raw payload which may contain session/value material.
Cmux User-Facing Error Privacy ❌ Error Line 8944 in Sources/TerminalController.swift exposes raw cookie payload in user-facing error, including sensitive "value" field, violating user-facing-errors.md rule against unredacted payload dumps. Redact error data to include only safe cookie fields (name, domain, path) instead of the full raw cookie object containing sensitive values.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR description covers the summary and verification sections but lacks testing procedures, demo video, review trigger checklist items, and formal sign-off. Add explicit testing steps (unit test commands), demo video if applicable, review trigger block, and checkbox completion status to match the required template format.
✅ Passed checks (19 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main objective: keeping blocking browser automation commands off the main actor to prevent UI hangs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PR correctly applies Swift actor isolation rules per .github/review-bot-rules/swift-actor-isolation.md: browser helpers properly marked nonisolated, MainActor-bound types (BrowserPanel, WKWebView)...
Cmux Expensive Synchronous Load ✅ Passed PR focuses on browser automation socket-worker dispatch; contains no RestorableAgentSessionIndex, agent hook stores, transcripts, trajectories, or other expensive agent-history loads on main/intera...
Cmux Cache Substitution Correctness ✅ Passed No cache substitution correctness violations found. The frameSelector in-memory cache appropriately persists app state without replacing fresh reads, and cookie retrieval is a fresh WKHTTPCookieSto...
Cmux No Hacky Sleeps ✅ Passed No hacky sleeps found. TypeScript/JS setTimeout uses (keyboard chord 700ms, copy feedback 1500-2000ms) are legitimate presentation timers allowed by rule.
Cmux Algorithmic Complexity ✅ Passed PR code uses optimal linear-time algorithms for bounded collections. Browser cookie/storage operations filter once per call (O(n) where n is browser cookies/storage entries, typically <1000). Opera...
Cmux Swift Concurrency ✅ Passed New v2AwaitCallback uses DispatchSemaphore exclusively to bridge third-party AppKit callback APIs (WKHTTPCookieStore, screenshot capture) required for browser automation on socket-worker lanes. T...
Cmux Swift File And Package Boundaries ✅ Passed PR adds 238 lines (below 250-line threshold) to existing oversized TerminalController.swift with focused browser-automation handler refactoring, no new mixed responsibilities created.
Cmux Swiftpm Lockfiles ✅ Passed PR includes 11 package-local Package.resolved changes paired with Package.swift changes, root Xcode Package.resolved paired with project.pbxproj, no .gitignore ignores Package.resolved.
Cmux Full Internationalization ✅ Passed PR passes internationalization check: error messages added are internal socket API responses for programmatic/script use (not UI text), no Swift UI/String(localized:) text added, no string catalog...
Cmux Swiftui State Layout ✅ Passed PR modifies business logic (socket control, execution policy) not SwiftUI Views; no new @Published/@StateObject/ObservableObject/GeometryReader/store-holding lazy containers introduced.
Cmux Architecture Rethink ✅ Passed PR moves blocking browser automation ops to socket-worker lane with explicit v2MainSync hops for WebKit/UI access—correctness fix with clear ownership (ControlCommandExecutionPolicy), named invaria...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR focuses on browser automation execution policy and handler refactoring, not on creating or materially changing standalone cmux-owned windows. No NSWindow/NSPanel/NSWindowController constructors...
Cmux Source Artifacts ✅ Passed All 9 changed files are intentional source, config, review rules, or test files; no prohibited artifacts (logs, caches, build output, DerivedData, etc.) were added to source control.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No test/debug seams added to production source. New browser automation functions (v2BrowserScreenshot, v2BrowserFrameSelect, v2BrowserDialogRespond, etc.) are clean production code with no #if DEBU...
Cmux No Ambient Global State ✅ Passed All new Swift code additions are properly scoped as private instance methods of TerminalController class. No public/internal top-level functions, mutable globals, singletons, or caseless enums intr...
Cmux Hot Path Allocating Formatting ✅ Passed PR contains no hot-path allocating formatting violations. String(format:) calls are in debug paths; all formatter allocations (ISO8601DateFormatter) are in cold paths (notifications, crash tracing,...
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-browser-eval-hang-audit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7ea1eae. Configure here.

Comment thread Sources/TerminalController.swift
@greptile-apps

greptile-apps Bot commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves 15+ blocking browser automation commands (screenshot, frame select, dialogs, cookies, storage, console/errors, state save/load, script/style injection) off the main actor and onto the socket-worker lane, matching the pattern already used for browser.eval, browser.click, and navigation commands. It also adds the new browser-automation-webkit-waits-off-main review rule and extends policy test coverage.

  • Handler functions are marked nonisolated and switched from v2BrowserWithPanel to v2BrowserWithPanelContext, which resolves the panel/WKWebView on main via v2MainSync and runs the blocking wait body on the worker. All WKHTTPCookieStore, screenshot callback, and WebKit UI mutations are wrapped in explicit v2MainSync hops.
  • ControlCommandExecutionPolicy.socketWorkerMethods and v2BrowserAutomationCommandOnSocketWorker are updated to enumerate every moved command; browser.get.title and browser.frame.main (no blocking waits) remain on main.
  • A new v2BrowserPanelFields helper consolidates duplicated workspace/surface response payload assembly across all moved handlers.

Confidence Score: 5/5

All WebKit waits now run off the main actor; WebKit/AppKit access and mutable-state updates stay inside explicit v2MainSync hops. The refactor is consistent across all 15 moved commands and matches the pre-existing browser.eval/navigate pattern exactly.

Every moved handler is nonisolated, uses v2BrowserWithPanelContext to resolve panels on main, and wraps WKHTTPCookieStore / screenshot / WKUserContentController calls inside v2MainSync before the blocking v2AwaitCallback wait. Policy tests assert worker routing for all moved commands and correctly verify that browser.get.title and browser.frame.main remain on main. No commands were missed, no WebKit mutations happen off-main without a hop, and the renamed v2BrowserAutomationCommandOnSocketWorker router is exhaustive.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TerminalController.swift Core production change: 15+ browser handlers made nonisolated, switched to v2BrowserWithPanelContext, WebKit/AppKit mutations wrapped in v2MainSync, worker router renamed and extended. Pattern is consistent with the pre-existing browser.eval/click/navigate migration.
Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift Adds all 15 moved commands to socketWorkerMethods; comment updated to describe the broader category of WebKit waits beyond just JS evaluation.
Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandExecutionPolicyTests.swift Worker-routing assertions extended for all moved commands; browser.screenshot and browser.cookies.get removed from the main-actor assertion list and replaced with browser.get.title and browser.frame.main, which correctly stay on main.
.github/review-bot-rules/browser-automation-webkit-waits-off-main.md New review rule file defining fail/pass criteria for browser socket automation commands; covers JS evals, WKHTTPCookieStore, screenshot callbacks, and panel/WebKit mutation isolation requirements.
.coderabbit.yaml Adds path-specific review instructions for TerminalController.swift and the two policy files, and adds a new 'cmux browser automation off-main' error-mode pre-merge check.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client as Socket Client
    participant Worker as Socket Worker Lane
    participant Main as Main Actor (v2MainSync)
    participant WK as WebKit / WKHTTPCookieStore

    Client->>Worker: browser.screenshot / browser.cookies.get / browser.frame.select / etc.
    Note over Worker: v2BrowserAutomationCommandOnSocketWorker()
    Worker->>Main: v2BrowserWithPanelContext resolve panel + webView
    Main-->>Worker: V2BrowserPanelContext (workspaceId, surfaceId, browserPanel, webView)
    Worker->>Main: "v2MainSync { store = webView.configuration...httpCookieStore }"
    Main-->>Worker: WKHTTPCookieStore reference
    Worker->>Main: "v2AwaitCallback { v2MainSync { store.getAllCookies { finish(items) } } }"
    Note over Worker: blocking wait off main
    Main->>WK: store.getAllCookies(completionHandler:)
    WK-->>Main: [HTTPCookie] callback
    Main-->>Worker: finish([HTTPCookie])
    Worker-->>Client: .ok(v2BrowserPanelFields(ctx, adding: [...]))

    Note over Client,WK: browser.get.title / browser.frame.main stay on Main Actor (no blocking wait)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client as Socket Client
    participant Worker as Socket Worker Lane
    participant Main as Main Actor (v2MainSync)
    participant WK as WebKit / WKHTTPCookieStore

    Client->>Worker: browser.screenshot / browser.cookies.get / browser.frame.select / etc.
    Note over Worker: v2BrowserAutomationCommandOnSocketWorker()
    Worker->>Main: v2BrowserWithPanelContext resolve panel + webView
    Main-->>Worker: V2BrowserPanelContext (workspaceId, surfaceId, browserPanel, webView)
    Worker->>Main: "v2MainSync { store = webView.configuration...httpCookieStore }"
    Main-->>Worker: WKHTTPCookieStore reference
    Worker->>Main: "v2AwaitCallback { v2MainSync { store.getAllCookies { finish(items) } } }"
    Note over Worker: blocking wait off main
    Main->>WK: store.getAllCookies(completionHandler:)
    WK-->>Main: [HTTPCookie] callback
    Main-->>Worker: finish([HTTPCookie])
    Worker-->>Client: .ok(v2BrowserPanelFields(ctx, adding: [...]))

    Note over Client,WK: browser.get.title / browser.frame.main stay on Main Actor (no blocking wait)
Loading

Reviews (6): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/TerminalController.swift (1)

9468-9494: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not return loaded: true after ignored restore failures.

Cookie conversion/set failures and storage JS failures are currently ignored, so callers can get success after a partial state restore. Return an error before the final OK response.

Propagate cookie and storage restore failures
             if let cookieRows = raw["cookies"] as? [[String: Any]] {
                 for row in cookieRows {
-                    if let cookie = v2BrowserCookieFromObject(row, fallbackURL: cookieContext.fallbackURL) {
-                        _ = v2BrowserCookieStoreSet(cookieContext.store, cookie: cookie)
+                    guard let cookie = v2BrowserCookieFromObject(row, fallbackURL: cookieContext.fallbackURL) else {
+                        return .err(
+                            code: "invalid_params",
+                            message: "Invalid cookie in state file",
+                            data: ["name": row["name"] as? String ?? ""]
+                        )
+                    }
+                    guard v2BrowserCookieStoreSet(cookieContext.store, cookie: cookie) else {
+                        return .err(code: "timeout", message: "Timed out setting cookie", data: ["name": cookie.name])
                     }
                 }
             }
@@
-                _ = v2RunBrowserJavaScript(ctx.webView, surfaceId: ctx.surfaceId, script: script, timeout: 10.0)
+                switch v2RunBrowserJavaScript(ctx.webView, surfaceId: ctx.surfaceId, script: script, timeout: 10.0) {
+                case .failure(let message):
+                    return .err(code: "js_error", message: message, data: nil)
+                case .success:
+                    break
+                }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TerminalController.swift` around lines 9468 - 9494, The code is
currently ignoring failures when setting browser cookies and executing the
storage JavaScript, which causes the function to return success even after
partial state restore failures. Instead of assigning the results of
v2BrowserCookieStoreSet and v2RunBrowserJavaScript calls to underscore, capture
their return values and check for error conditions. Additionally, track when
v2BrowserCookieFromObject returns nil for a cookie row. If any of these
operations fail, propagate an appropriate error to the caller rather than
allowing the function to return a success response (loaded: true).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/TerminalController.swift`:
- Around line 8943-8944: The error response in the guard statement for
v2BrowserCookieFromObject is exposing the raw cookie payload in the data
dictionary, which may contain sensitive session material and credential
information. Remove the raw cookie from the error data field or replace it with
only safe, non-sensitive fields such as cookie name or domain. Do not return the
unredacted cookie payload in the API error response.
- Around line 9396-9400: The code currently treats a timeout from
v2BrowserCookieStoreAll(store) as an empty cookie list by using the
nil-coalescing operator with an empty array, which silently succeeds with no
cookies when a timeout occurs. Instead, modify the cookie snapshot logic to
explicitly check if v2BrowserCookieStoreAll(store) returns nil and propagate
that failure (timeout) rather than defaulting to an empty array, ensuring the
state save operation fails when cookie retrieval times out rather than
succeeding with incomplete data.
- Around line 7378-7386: The PNG encoding operation via v2PNGData(from: image)
is currently executing inside the WebKit callback, which blocks the callback
thread. Move the PNG conversion outside of the v2AwaitCallback block to happen
after it returns. In the .success case inside
captureAutomationVisibleViewportSnapshot callback, pass the image directly to
finish() instead of converting it to PNG data. After v2AwaitCallback completes
and returns the image, perform the PNG encoding on the socket worker thread
where the result is processed.

---

Outside diff comments:
In `@Sources/TerminalController.swift`:
- Around line 9468-9494: The code is currently ignoring failures when setting
browser cookies and executing the storage JavaScript, which causes the function
to return success even after partial state restore failures. Instead of
assigning the results of v2BrowserCookieStoreSet and v2RunBrowserJavaScript
calls to underscore, capture their return values and check for error conditions.
Additionally, track when v2BrowserCookieFromObject returns nil for a cookie row.
If any of these operations fail, propagate an appropriate error to the caller
rather than allowing the function to return a success response (loaded: true).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 925df2ba-07a6-4a7d-b74a-96358830ed05

📥 Commits

Reviewing files that changed from the base of the PR and between 510ff81 and 35b71ef.

📒 Files selected for processing (9)
  • .coderabbit.yaml
  • .github/review-bot-rules/README.md
  • .github/review-bot-rules/browser-automation-webkit-waits-off-main.md
  • .greptile/config.json
  • .greptile/files.json
  • .greptile/rules.md
  • Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swift
  • Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandExecutionPolicyTests.swift
  • Sources/TerminalController.swift

Comment on lines 7378 to +7386
let snapshotResult: Data?? = v2AwaitCallback(timeout: 15.0) { finish in
browserPanel.captureAutomationVisibleViewportSnapshot { result in
switch result {
case .success(let image):
finish(self.v2PNGData(from: image))
case .failure:
finish(nil)
v2MainSync {
browserPanel.captureAutomationVisibleViewportSnapshot { result in
switch result {
case .success(let image):
finish(self.v2PNGData(from: image))
case .failure:
finish(nil)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Keep PNG encoding off the WebKit capture callback.

Line 7383 converts the snapshot to TIFF/PNG before signaling the worker wait. For large screenshots, that still keeps CPU-heavy encoding on the WebKit/AppKit callback path. Return the captured image first, then encode after v2AwaitCallback resumes on the socket worker.

Move PNG conversion after the main/WebKit callback returns
-        let snapshotResult: Data?? = v2AwaitCallback(timeout: 15.0) { finish in
+        let snapshotResult: NSImage?? = v2AwaitCallback(timeout: 15.0) { finish in
             v2MainSync {
                 browserPanel.captureAutomationVisibleViewportSnapshot { result in
                     switch result {
                     case .success(let image):
-                        finish(self.v2PNGData(from: image))
+                        finish(image)
                     case .failure:
                         finish(nil)
                     }
                 }
             }
@@
-        guard let imageData = snapshotResult else {
+        guard let image = snapshotResult else {
             return .err(code: "internal_error", message: "Failed to capture snapshot", data: nil)
         }
+        guard let imageData = v2PNGData(from: image) else {
+            return .err(code: "internal_error", message: "Failed to encode snapshot", data: nil)
+        }

As per path instructions, WebKit-waiting browser automation should do the minimum UI hop and keep waits/work on the socket-worker lane.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TerminalController.swift` around lines 7378 - 7386, The PNG encoding
operation via v2PNGData(from: image) is currently executing inside the WebKit
callback, which blocks the callback thread. Move the PNG conversion outside of
the v2AwaitCallback block to happen after it returns. In the .success case
inside captureAutomationVisibleViewportSnapshot callback, pass the image
directly to finish() instead of converting it to PNG data. After v2AwaitCallback
completes and returns the image, perform the PNG encoding on the socket worker
thread where the result is processed.

Source: Path instructions

Comment thread Sources/TerminalController.swift
Comment on lines +9396 to +9400
let store = v2MainSync {
ctx.webView.configuration.websiteDataStore.httpCookieStore
}
let cookies = (v2BrowserCookieStoreAll(store) ?? []).map(v2BrowserCookieDict)
let stateSnapshot = v2MainSync {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail state save when cookie snapshot times out.

Line 9399 treats a cookie-store timeout as an empty cookie list, then writes a successful state file that silently drops cookies. Match browser.cookies.get and return a timeout instead.

Preserve cookie snapshot failures
             let store = v2MainSync {
                 ctx.webView.configuration.websiteDataStore.httpCookieStore
             }
-            let cookies = (v2BrowserCookieStoreAll(store) ?? []).map(v2BrowserCookieDict)
+            guard let cookieRows = v2BrowserCookieStoreAll(store) else {
+                return .err(code: "timeout", message: "Timed out reading cookies", data: nil)
+            }
+            let cookies = cookieRows.map(v2BrowserCookieDict)
             let stateSnapshot = v2MainSync {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TerminalController.swift` around lines 9396 - 9400, The code
currently treats a timeout from v2BrowserCookieStoreAll(store) as an empty
cookie list by using the nil-coalescing operator with an empty array, which
silently succeeds with no cookies when a timeout occurs. Instead, modify the
cookie snapshot logic to explicitly check if v2BrowserCookieStoreAll(store)
returns nil and propagate that failure (timeout) rather than defaulting to an
empty array, ensuring the state save operation fails when cookie retrieval times
out rather than succeeding with incomplete data.

@lawrencecchen
lawrencecchen merged commit 1d98dc7 into main Jun 23, 2026
34 of 36 checks passed
@lawrencecchen
lawrencecchen deleted the feat-browser-eval-hang-audit branch June 23, 2026 13:22
@coderabbitai coderabbitai Bot mentioned this pull request Jun 26, 2026
5 tasks done
@lawrencecchen
lawrencecchen restored the feat-browser-eval-hang-audit branch July 18, 2026 10:24

This branch was successfully deployed

1 active deployment
Preview – cmux — a4209bc5 Deployed Jun 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant