Skip to content

Fix browser and Codex helper lifecycle leaks - #4191

Closed
austinywang wants to merge 31 commits into
mainfrom
issue-4188-memory-helper-leaks
Closed

austinywang wants to merge 31 commits into
mainfrom
issue-4188-memory-helper-leaks

Conversation

@austinywang

@austinywang austinywang commented May 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4188

Summary

  • add explicit WKWebView release teardown on browser pane close, profile/workspace webview replacement, popup close, panel deinit, and app termination
  • remove WebKit script message handlers and panel closures during teardown so WebKit resources can release
  • give Codex transcript monitors an owning app pid for local sockets, skip local owner-pid watchers for relay-backed sockets, retire previous session monitor leases only when a replacement lease is ready, and clean same-tag monitor helpers during dev reload replacement

Verification

  • Reproduced locally before the fix with ps: 103 com.apple.WebKit.WebContent helpers totaling about 10.5 GB RSS, 99 orphaned to ppid=1; 9 cmux hooks codex monitor helpers totaling about 1.1 GB RSS, 5 orphaned
  • Added focused regression coverage for monitor lease failure, missing/invalid owner pid, owner process exit, and relay-backed prompt-submit using a lease-backed monitor without --owner-pid
  • Did not run local tests per repository/user policy; CI is the test verification path, and after all required checks were green I built and launched the tagged dev app with CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-4188-memory-helper-leaks --launch.

Note

Medium Risk
Touches app termination, NSWindow swizzling, and CLI monitor startup rules; mistakes could close the wrong inspector window or stop monitors early, but changes are scoped to teardown and helper lifecycle rather than auth or data paths.

Overview
Addresses orphaned WebKit helpers and lingering cmux hooks codex monitor processes by tightening browser teardown and making Codex transcript monitors owner-aware.

Browser / WebKit: Adds a shared, idempotent WKWebView release path (stop load, clear ReactGrab and paste handlers/user scripts, nil delegates, about:blank, remove from hierarchy) on panel close, profile/context resets, popup close/deinit, and app quit via teardownAllWorkspacePanelsBeforeAppTeardown. CmuxWebView gains cmuxPrepareForReleaseTeardown() for closure/handler cleanup.

Web Inspector: Stale detached “Web Inspector” windows are orderOut (with ownership checks on panel/popup) instead of forcibly closed; NSWindow.close / close-button handling routes through orderOutStaleBrowserWebInspectorWindowIfNeeded so live inspectors tied to a panel or popup are kept.

Codex monitors: system.identify returns app_pid; local sockets pass --owner-pid and exit when that process dies (DispatchSourceProcess + faster 10s owner polls). Relay-backed sockets skip owner PID / system.identify for monitor start. Monitors are not started without a valid owner PID (local) or a successful lease; scripts/reload.sh pkills lingering monitor helpers on tagged reload. Regression tests cover owner exit, relay behavior, lease failure, and invalid --owner-pid.

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


Summary by cubic

Fixes browser and Codex helper lifecycle leaks to reduce stray WebKit and monitor processes. Web views and popups now tear down idempotently, popup inspector ownership is preserved during close, stale Web Inspector windows are ordered out only when not owned, and Codex monitors are owner‑aware and exit fast.

  • Bug Fixes
    • Browser: Centralized, idempotent WKWebView teardown on profile/context reset, webview swap, popup close, panel close/deinit, and app quit. Stops loads, clears ReactGrab and paste‑as‑plain‑text handlers/user scripts, nils delegates, loads about:blank, removes the view; CmuxWebView adds cmuxPrepareForReleaseTeardown. Swizzled NSWindow.close and sendEvent orderOut (not close) stale “Web Inspector” windows and hide them from the Windows menu; live detached inspectors owned by a panel or popup are preserved (ownership kept through popup close).
    • Codex: system.identify now returns app_pid. Local sockets require a valid owner PID and pass --owner-pid; relay sockets skip owner PID and system.identify. Monitors watch the owner with a DispatchSourceProcess, poll every 10s, wake filesystem waits, and exit promptly on owner death; start is skipped if owner PID is missing/invalid or lease write fails. scripts/reload.sh reaps lingering cmux hooks codex monitor. Tests cover relay owner‑PID skipping, invalid owner input, lease failure, and owner‑exit behavior.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Centralized and hardened web-view release teardown across panels, popups and app termination; ensures handlers, delegates and focus state are cleared.
  • New Features

    • Owner-aware monitor support with more frequent owner checks, owner-PID validation and prompt exit when owner dies; system identity now exposes app PID.
  • Tests

    • Regression tests for owner-PID lifecycle, ownerless behavior and invalid owner-PID handling.
  • Chores

    • Reload script now force-terminates lingering monitor processes.

Review Change Stack

@vercel

vercel Bot commented May 15, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Error Error Jun 7, 2026 9:57am
cmux-staging Building Building Preview, Comment Jun 7, 2026 9:57am

@coderabbitai

coderabbitai Bot commented May 15, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes WKWebView release teardown across panels and popups, introduces CmuxWebView and ReactGrab safe teardown hooks, ensures panels are torn down on app termination, and adds owner-PID propagation and an owner-exit watcher so the Codex monitor exits promptly; regression tests and reload cleanup were added for the monitor lifecycle.

Changes

Resource Lifecycle and Helper Process Management

Layer / File(s) Summary
Owner PID tracking and monitor watcher
Sources/TerminalController.swift, CLI/cmux.swift, scripts/reload.sh
system.identify now includes app_pid. Codex monitor fetches owner PID, validates --owner-pid, appends the flag when spawning the monitor, and uses a CodexMonitorOwnerExitWatcher (kill(pid,0) + DispatchSourceProcess) with poll interval reduced to 10s to exit early when owner dies; wait logic accepts the watcher. Reload script also pkills codex monitor subprocesses for tagged-app cleanup.
Codex monitor wait/hook flow and tests
CLI/cmux.swift, tests/test_codex_feed_hooks.py
waitForCodexTranscriptChange accepts an optional ownerExitWatcher and properly registers/unregisters waiters and cancels filesystem sources; the codex hook retires leases with turnId: nil, includes has_owner_pid breadcrumbs, and emits owner-unavailable when owner PID is missing. Tests add ownerless and --owner-pid lifecycle regressions: gone, live-exit, and invalid input cases.
CmuxWebView pre-release teardown
Sources/Panels/CmuxWebView.swift
Adds cmuxPrepareForReleaseTeardown() to clear context-menu callback state and remove the installed paste-as-plain-text WKScriptMessageHandler, resetting its installed flag.
ReactGrab message-handler tracking
Sources/Panels/ReactGrab.swift
Introduce Objective-C associated-object key to track handler installation, remove previous handler before adding a new one, and provide conditional removal that clears the flag to avoid double-removal or dangling handlers.
BrowserPanel unified WebView teardown
Sources/Panels/BrowserPanel.swift
Add cmuxResponderChainContains, didTearDownWebViewForRelease, and unified teardown helpers that close inspector state, clear first-responder, detach portal registry, stop loading, remove ReactGrab handlers and user scripts, call CmuxWebView pre-release hook, nil delegates, load blank page, and remove the web view. Apply across profile switching, content-process replacement, close, deinit, and workspace resets; update focus/ownership checks.
BrowserPopupWindowController teardown
Sources/Panels/BrowserPopupWindowController.swift
Add idempotent tearDownWebViewForRelease() with didTearDownWebView guard and call it from windowWillClose, performing full release cleanup for popup windows.
App-level panel cleanup on termination
Sources/AppDelegate.swift
Add teardownAllWorkspacePanelsBeforeAppTeardown() and call it early in applicationWillTerminate to ensure all panels run full teardown before app shutdown.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #4189: WebView/WebInspector teardown and handler removal in this PR address memory-retention and inspector state concerns similar to the issue.

Possibly related PRs

Poem

🐇 I tidy up the web and wire,

Blank pages hush and handlers tire,
Monitors watch an owner go,
Then gently stop — the helper's slow,
🥕 The burrow's neat; the rabbit's choir.


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (3 errors, 1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error CLI/cmux.swift introduces DispatchSemaphore blocking waits (line 18934), 10-second polling loop (line 18822), and NSLock in production codex monitor code, violating swift-blocking-runtime.md. Replace DispatchSemaphore with async/await. Remove polling loop; use event-driven DispatchSource handlers. Use actor-isolated state instead of NSLock.
Cmux Swift @Concurrent ❌ Error ReactGrabScriptLoader.fetch() is network-heavy without @concurrent, called from @MainActor BrowserPanel. Uses Task.detached() internally but function needs @concurrent annotation. Add @concurrent to ReactGrabScriptLoader.fetch() to indicate it leaves caller's isolation. The heavy I/O work in Task.detached() requires explicit concurrency boundary at the function level.
Cmux Architecture Rethink ❌ Error Observer clearing wired differently across paths: profileSwitch/contextReset clear before teardown; panelClose after. Violates rule against same behavior on multiple surfaces. Move webViewObservers.removeAll() and webViewCancellables.removeAll() before tearDownCurrentWebViewForRelease() in close() to match other lifecycle paths.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Actor Isolation ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
Description check ❓ Inconclusive The PR description provides comprehensive summaries at multiple levels (author, Cursor, cubic) detailing changes to WebView teardown and Codex monitor lifecycle, with verification notes but no explicit testing section following the template structure. Add a dedicated 'Testing' section explicitly describing how the change was tested locally and what was verified manually, as specified in the template.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: fixing lifecycle leaks in browser helpers and Codex transcript monitors.
Linked Issues check ✅ Passed The code changes comprehensively implement all requirements from issue #4188: explicit WKWebView teardown on lifecycle events, Codex monitor binding to owner PID with exit detection, and new regression tests.
Out of Scope Changes check ✅ Passed All code changes are directly scoped to fixing lifecycle leaks per issue #4188; no unrelated changes detected.
Cmux No Hacky Sleeps ✅ Passed PR adds no hacky sleeps in production code. Test polling for process lifecycle is deterministic scaffolding. Shell adds only pkill command. Swift uses proper event handlers.
Cmux Swift Concurrency ✅ Passed PR introduces DispatchSourceProcess and DispatchSemaphore for process exit and file system monitoring. These are required OS API boundaries with no async/await alternatives - allowed per rules.
Cmux Swift File And Package Boundaries ✅ Passed PR complies with file/package boundaries. No oversized file violations (cmux +191, BrowserPanel +21 net lines), no mixed responsibilities, focused bug-fix additions only.
Cmux Swift Logging ✅ Passed No logging violations found. The PR adds lifecycle teardown code without introducing print(), NSLog(), debugPrint(), dump(), or Logger violations in production Swift code.
Cmux User-Facing Error Privacy ✅ Passed All user-facing errors comply with privacy rules. Error message uses generic language, breadcrumbs are safe identifiers, app_pid is a standard process ID, and no sensitive data is exposed.
Cmux Swiftui State Layout ✅ Passed PR has no SwiftUI violations. All modified files are AppKit/CLI/test code with no new SwiftUI state patterns introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR modifies existing window teardown logic but introduces no new standalone NSWindow/NSPanel/NSWindowController classes or identifiers. No new cmux.* identifiers added. Lint script passes.
✨ 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 issue-4188-memory-helper-leaks

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 and usage tips.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 15, 2026

@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: 2

🤖 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 `@CLI/cmux.swift`:
- Line 18693: The current code silently converts malformed --owner-pid values to
nil (ownerPID) which later treats it as "alive"; change the parsing so that you
first check optionValue(commandArgs, name: "--owner-pid") for presence, then
attempt Int.init and validate the result is a positive integer; if the option is
present but parsing fails or value <= 0, print an error message and exit
non‑zero (fail fast) instead of treating it as nil. Update the ownerPID
initialization logic (the use of optionValue/ownerPID) to perform this explicit
presence+validation check and call the existing exit/error path when invalid
input is detected.

In `@Sources/AppDelegate.swift`:
- Line 1559: The code currently builds managers using the debug-filtered
allMainWindowTabManagersForDebug(), which can omit valid TabManager instances;
replace that call with the non-debug set of all registered tab managers (use the
project’s canonical "all registered" accessor instead of
allMainWindowTabManagersForDebug()), still append the optional tabManager via
[tabManager].compactMap{ $0 }, and then iterate those managers to call
teardownAllPanels() on each so no panels are skipped during termination.
🪄 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: 34b78e2a-9165-46e4-965f-39388fd2ef4e

📥 Commits

Reviewing files that changed from the base of the PR and between c83f585 and 42adc84.

📒 Files selected for processing (9)
  • CLI/cmux.swift
  • Sources/AppDelegate.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPopupWindowController.swift
  • Sources/Panels/CmuxWebView.swift
  • Sources/Panels/ReactGrab.swift
  • Sources/TerminalController.swift
  • scripts/reload.sh
  • tests/test_codex_feed_hooks.py

Comment thread CLI/cmux.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated
@greptile-apps

greptile-apps Bot commented May 15, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two categories of resource leaks: orphaned WKWebView/WebContent processes from browser panels and popups, and long-running cmux hooks codex monitor helpers that outlived their owner app. The WebKit side introduces a centralized idempotent tearDownWebViewForRelease path (stop load, clear handlers/scripts, nil delegates, load blank, remove from hierarchy) called at every panel/popup/app-quit teardown point, plus a new NSWindow.close swizzle that orders out stale Web Inspector windows rather than closing them. The Codex side ties each local-socket monitor to the app's PID via a DispatchSourceProcess exit watcher and restructures the prompt-submit startup sequence to skip monitors when the owner PID or lease creation fails.

  • WebKit teardown: Adds didTearDownWebViewForRelease / didTearDownWebView guard flags to BrowserPanel and BrowserPopupWindowController, shared cmuxResponderChainContains and cmuxViewTreeContains free functions, CmuxWebView.cmuxPrepareForReleaseTeardown, and app-quit-time teardownAllWorkspacePanelsBeforeAppTeardown; stale detached Web Inspector windows are ordered out (not closed) via ownsLiveDetachedWebInspectorWindow ownership checks across all panels/popups.
  • Codex monitors: system.identify now returns app_pid; local sockets require a valid owner PID passed as --owner-pid; a CodexMonitorOwnerExitWatcher (DispatchSourceProcess) wakes the monitor's filesystem wait immediately on owner exit; relay sockets skip owner-PID logic entirely; lease creation now gates both lease retirement and monitor start.
  • Tests: Regression coverage added for owner-PID exit, zero/invalid PID, missing owner (local socket), and relay-backed prompt-submit.

Confidence Score: 5/5

Safe to merge; teardown paths are idempotent and guarded, Codex monitor changes are well-tested, and the previously-flagged issues are addressed.

Both the WebKit teardown and Codex monitor changes are carefully structured: idempotency flags prevent double-teardown, the DispatchSourceProcess watcher replaces the polling-only kill(0) check, and the prompt-submit lease/retire ordering is now correct. The one remaining note (no early title guard in the AppDelegate close-hook) is a minor hot-path optimisation, not a correctness issue. The regression test suite directly covers the failure modes this PR was introduced to fix.

Sources/AppDelegate.swift — the NSWindow.close swizzle path and orderOutStaleBrowserWebInspectorWindowIfNeeded are the highest-surface-area additions and worth a quick read-through during QA.

Important Files Changed

Filename Overview
CLI/cmux.swift Adds CodexMonitorOwnerExitWatcher (DispatchSourceProcess-backed), owner-PID flag to monitor subcommand, relay-socket branching in prompt-submit, and fixes defer placement for lease cleanup on early exit; logic is well-structured and the previous-thread issues are addressed.
Sources/AppDelegate.swift Adds NSWindow.close swizzle, app-quit panel teardown, and orderOutStaleBrowserWebInspectorWindowIfNeeded; the AppDelegate function collects all browser panels on every NSWindow.close call without an early title guard, which could be tightened.
Sources/Panels/BrowserPanel.swift Centralises WKWebView teardown behind an idempotent guard flag; extracts cmuxResponderChainContains/cmuxViewTreeContains as shared free functions; popup ordering fix during panelClose; deinit now skips teardown when already done.
Sources/Panels/BrowserPopupWindowController.swift Adds didTearDownWebView guard, idempotent tearDownWebViewForRelease, and ownsLiveDetachedWebInspectorWindow; consistent with BrowserPanel teardown pattern.
Sources/Panels/CmuxWebView.swift Adds cmuxPrepareForReleaseTeardown clearing context-menu closures and the paste-as-plain-text script message handler via associated-object guard; clean and correct.
Sources/Panels/ReactGrab.swift Guards ReactGrab handler installation/removal with an associated-object key to prevent double-remove crashes; adds tearDownReactGrabStateForWebViewRelease for panel teardown.
tests/test_codex_feed_hooks.py Adds FakeRelayCmuxSocket with HMAC relay-auth, app_pid injection, regression tests for owner-exit/invalid-PID/missing-owner/relay-backed scenarios, and renames the lease-failure test to correctly describe skipping behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[prompt-submit / codex hook] --> B{client.isRelayBacked?}
    B -- Yes --> C[skip system.identify\nskip owner-PID]
    B -- No --> D[codexMonitorOwnerPID\nsystem.identify RPC]
    D -- nil returned --> E[log owner-unavailable\nskip monitor start]
    D -- valid PID --> F[createCodexMonitorLease]
    C --> F
    F -- failure --> G[log lease-unavailable\nskip monitor start]
    F -- leasePath --> H[retireCodexMonitorLeases\npreserving new lease]
    H --> I[startCodexTranscriptMonitor\n--owner-pid only for local]
    I --> J{monitor loop}
    J -- ownerExitWatcher.hasExited --> K[exit: owner dead]
    J -- lease retired --> L[exit: replaced by newer monitor]
    J -- deadline --> M[exit: 4-hour max]
    J -- wait --> N[waitForCodexTranscriptChange\nDispatchSourceFileSystemObject\nDispatchSourceProcess waker]
    N --> J
Loading

Reviews (15): Last reviewed commit: "fix: keep popup inspector ownership duri..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift Outdated
Comment thread Sources/Panels/BrowserPanel.swift Outdated
Comment thread Sources/Panels/BrowserPopupWindowController.swift Outdated
Comment thread CLI/cmux.swift

@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.

Caution

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

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

3094-3100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reset loading state when swapping out the current WKWebView.

These two replacement paths tear down the active web view without clearing the panel’s pending loading state. If the old view is replaced mid-navigation and the new view does not immediately emit a fresh loading cycle, isLoading can stay stuck true and the old loadingEndWorkItem can outlive the retired view.

Suggested fix
         webViewObservers.removeAll()
         webViewCancellables.removeAll()
         faviconTask?.cancel()
         faviconTask = nil
         faviconRefreshGeneration &+= 1
+        loadingEndWorkItem?.cancel()
+        loadingEndWorkItem = nil
+        loadingGeneration &+= 1
+        loadingStartedAt = nil
+        isLoading = false
         tearDownCurrentWebViewForRelease(previousWebView, reason: "profileSwitch")
         webViewObservers.removeAll()
         webViewCancellables.removeAll()
         faviconTask?.cancel()
         faviconTask = nil
         faviconRefreshGeneration &+= 1
+        loadingEndWorkItem?.cancel()
+        loadingEndWorkItem = nil
+        loadingGeneration &+= 1
+        loadingStartedAt = nil
+        isLoading = false
         tearDownCurrentWebViewForRelease(oldWebView, reason: reason)

Also applies to: 3447-3452

🤖 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/Panels/BrowserPanel.swift` around lines 3094 - 3100, When replacing
the current WKWebView (the code path that calls webViewObservers.removeAll(),
webViewCancellables.removeAll(), faviconTask?.cancel(), faviconTask = nil,
faviconRefreshGeneration &+= 1, and
tearDownCurrentWebViewForRelease(previousWebView, reason: "profileSwitch")),
also reset the panel’s loading state to avoid isLoading getting stuck:
explicitly set isLoading = false and cancel/nil any pending loadingEndWorkItem
(and clear any pending navigation-related state) so the old view’s loading
callbacks cannot keep the panel marked as loading after the view is torn down;
apply the same change to the other replacement site around lines 3447-3452.
🤖 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.

Outside diff comments:
In `@Sources/Panels/BrowserPanel.swift`:
- Around line 3094-3100: When replacing the current WKWebView (the code path
that calls webViewObservers.removeAll(), webViewCancellables.removeAll(),
faviconTask?.cancel(), faviconTask = nil, faviconRefreshGeneration &+= 1, and
tearDownCurrentWebViewForRelease(previousWebView, reason: "profileSwitch")),
also reset the panel’s loading state to avoid isLoading getting stuck:
explicitly set isLoading = false and cancel/nil any pending loadingEndWorkItem
(and clear any pending navigation-related state) so the old view’s loading
callbacks cannot keep the panel marked as loading after the view is torn down;
apply the same change to the other replacement site around lines 3447-3452.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 819de641-396c-4b5b-9e65-e598aac5117b

📥 Commits

Reviewing files that changed from the base of the PR and between 42adc84 and 93db722.

📒 Files selected for processing (5)
  • CLI/cmux.swift
  • Sources/AppDelegate.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/BrowserPopupWindowController.swift
  • tests/test_codex_feed_hooks.py

coderabbitai[bot]
coderabbitai Bot previously requested changes May 15, 2026

@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)
CLI/cmux.swift (1)

20998-21015: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not spawn the monitor when owner PID discovery fails.

ownerPID is optional here, so a system.identify timeout or malformed/missing app_pid still falls through to startCodexTranscriptMonitor(...) without --owner-pid. That recreates the orphan-helper path this PR is trying to close on the failure path. Treat missing owner PID as a failed monitor launch and breadcrumb it instead of starting an unowned helper.

🤖 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 `@CLI/cmux.swift` around lines 20998 - 21015, The code currently calls
startCodexTranscriptMonitor even when codexMonitorOwnerPID(client: client)
returns nil; change the control flow so that if ownerPID is nil you do not spawn
the monitor: emit a telemetry.breadcrumb (e.g.,
"codex-hook.monitor.owner-unavailable" or reuse the existing breadcrumb pattern)
with has_owner_pid: false and any relevant context, and return/skip calling
startCodexTranscriptMonitor; keep codexMonitorOwnerPID and
startCodexTranscriptMonitor as the unique symbols to locate the change and
ensure no --owner-pid-less monitor is started on owner PID discovery failure.
🤖 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 `@CLI/cmux.swift`:
- Around line 18772-18780: Update the CLI error thrown when parsing --owner-pid
(in the block using optionValue(commandArgs, name: "--owner-pid") and assigning
ownerPID) to include the command name "cmux hooks codex monitor" and a concrete
recovery step; replace the current generic CLIError message with one that names
the failing command and suggests the user provide a positive integer (e.g.,
"cmux hooks codex monitor: invalid --owner-pid; please provide a positive
integer PID and retry") so the CLIError thrown by this parsing branch contains
both context and an explicit next action.

In `@tests/test_codex_feed_hooks.py`:
- Line 530: The test creates a subprocess with a hardcoded "/bin/sleep" binary
when assigning owner via subprocess.Popen([...]); replace the hardcoded path
with a portable lookup using shutil.which("sleep") (and fall back or assert if
None) so the subprocess uses the system sleep binary found at runtime; update
the owner = subprocess.Popen(...) call to use the resolved path variable instead
of "/bin/sleep" and mirror the same approach used at the earlier occurrence
around line 483.
- Line 483: The test uses a hardcoded "/bin/sleep" when launching the subprocess
(owner = subprocess.Popen(["/bin/sleep", "0.1"])); replace that hardcoded path
with a portable lookup using shutil.which("sleep") and handle the case where
which returns None (fail the test or skip). Update the subprocess.Popen call to
use the resolved path variable and add a clear error/skip if
shutil.which("sleep") returns None so the test remains portable across systems.

---

Outside diff comments:
In `@CLI/cmux.swift`:
- Around line 20998-21015: The code currently calls startCodexTranscriptMonitor
even when codexMonitorOwnerPID(client: client) returns nil; change the control
flow so that if ownerPID is nil you do not spawn the monitor: emit a
telemetry.breadcrumb (e.g., "codex-hook.monitor.owner-unavailable" or reuse the
existing breadcrumb pattern) with has_owner_pid: false and any relevant context,
and return/skip calling startCodexTranscriptMonitor; keep codexMonitorOwnerPID
and startCodexTranscriptMonitor as the unique symbols to locate the change and
ensure no --owner-pid-less monitor is started on owner PID discovery failure.
🪄 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: 394becfb-6899-4b8e-8256-63ff5a37a829

📥 Commits

Reviewing files that changed from the base of the PR and between 93db722 and 20dd4f3.

📒 Files selected for processing (2)
  • CLI/cmux.swift
  • tests/test_codex_feed_hooks.py

Comment thread CLI/cmux.swift
Comment thread tests/test_codex_feed_hooks.py Outdated
Comment thread tests/test_codex_feed_hooks.py Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 16, 2026

@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: 2

🤖 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 `@CLI/cmux.swift`:
- Around line 18772-18784: The code currently parses --owner-pid (ownerPIDRaw ->
parsedOwnerPID) and throws CLIError before registering the lease-file cleanup
defer, leaving the lease file behind on parse errors; move or add the lease
cleanup registration (the defer that removes the lease file) so it runs
unconditionally before parsing/validating ownerPID (i.e., ensure the defer that
cleans the lease file is installed immediately after the lease file is
created/recognized and before any guards that can throw), and apply the same
change to the analogous block referenced around ownerPID usage (the other
occurrence noted) so both error paths will always trigger the cleanup.
- Around line 20993-21023: The current code calls retireCodexMonitorLeases(...)
before codexMonitorOwnerPID(client:), causing the existing monitor to be torn
down if owner lookup times out; change the flow so you first call
codexMonitorOwnerPID(client:) and only if it returns a non-nil ownerPID then
call retireCodexMonitorLeases(sessionId:..., turnId: nil, env: ...) (or
otherwise delay retirement) and proceed to createCodexMonitorLease(...) and
startCodexTranscriptMonitor(...); update the block around codexMonitorOwnerPID,
retireCodexMonitorLeases, createCodexMonitorLease, and
startCodexTranscriptMonitor to ensure retirement happens after a successful
owner lookup.
🪄 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: 252472c5-c422-45ce-86ce-56ee1f1876d2

📥 Commits

Reviewing files that changed from the base of the PR and between 20dd4f3 and 15aabd5.

📒 Files selected for processing (2)
  • CLI/cmux.swift
  • tests/test_codex_feed_hooks.py

Comment thread CLI/cmux.swift
Comment thread CLI/cmux.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

No issues found across 9 files

Re-trigger cubic

Comment thread CLI/cmux.swift
Comment thread CLI/cmux.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 16, 2026

@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: 1

🤖 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/Panels/BrowserPanel.swift`:
- Around line 3823-3830: Move the observer/cancellable cleanup to occur before
calling tearDownCurrentWebViewForRelease in close(): clear
webViewObservers.removeAll(), webViewCancellables.removeAll() (and cancel
loadingEndWorkItem if needed) prior to calling
tearDownCurrentWebViewForRelease(webView, reason: "panelClose") so any
KVO/Combine callbacks enqueued by stopLoading()/loadHTMLString("") cannot run
and repopulate state; keep the existing
navigationDelegate/uiDelegate/webViewDidRequestClose niling and
loadingEndWorkItem cancellation after teardown as appropriate.
🪄 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: 3e69ab04-bd66-4cc5-acdc-108439d1e15b

📥 Commits

Reviewing files that changed from the base of the PR and between 15aabd5 and b5490fe.

📒 Files selected for processing (3)
  • CLI/cmux.swift
  • Sources/AppDelegate.swift
  • Sources/Panels/BrowserPanel.swift

Comment thread Sources/Panels/BrowserPanel.swift Outdated

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/AppDelegate.swift">

<violation number="1" location="Sources/AppDelegate.swift:14045">
P2: The new global live-window check is too broad and can prevent stale Web Inspector windows from being ordered out whenever any panel uses detached devtools.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic

Comment thread Sources/AppDelegate.swift Outdated
@cubic-dev-ai

cubic-dev-ai Bot commented May 17, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

Comment thread Sources/AppDelegate.swift
Comment thread Sources/Panels/BrowserPopupWindowController.swift
Comment thread Sources/Panels/BrowserPanel.swift
Comment thread CLI/cmux.swift
Comment thread Sources/Panels/BrowserPanel.swift Outdated

@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 6679257. Configure here.

Comment thread Sources/Panels/BrowserPanel.swift
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch had an error being deployed

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

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix 80GB memory growth from retained WebKit and cmux monitor helpers

3 participants