Skip to content

Fix tab-switch crash in vertical sidebar: defer hosted-inspector side-dock promotion out of the layout pass (#6150) - #6340

Merged
austinywang merged 25 commits into
mainfrom
issue-6150
Jun 19, 2026
Merged

austinywang merged 25 commits into
mainfrom
issue-6150

Conversation

@austinywang

@austinywang austinywang commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes the intermittent tab-switch crash in vertical sidebar mode reported in #6150 (macOS 14.8.2, still present on 0.64.x) — EXC_BAD_ACCESS in objc_msgSend from ___NSViewLayout_block_invoke (group A) and EXC_BREAKPOINT during layout (group B) — the same root-cause family as #653.

Root cause

HostContainerView.layout() (in Sources/Panels/BrowserPanelView.swift) synchronously called promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded(). When a docked DevTools/inspector split is present, that promotion mutates the view hierarchy inside the layout pass:

  • ensureHostedInspectorSideDockContainerView() → addSubview(...) + NSLayoutConstraint.activate(...)
  • moveHostedInspectorSubviewIfNeeded(...) → removeFromSuperview() + addSubview(...)

Mutating the view hierarchy synchronously inside an AppKit layout pass re-enters the layout machinery, which is exactly the documented crash:

This was the only synchronous view-hierarchy mutation reachable from the layout() override. The other mutation paths already defer:

  • the dock-config path via scheduleHostedInspectorDockConfigurationSync (DispatchQueue.main.async), and
  • viewDidMoveToWindow / viewDidMoveToSuperview via the deferred scheduleHostedInspectorDividerReapply work item (which itself calls promote… off the layout pass).

Fix

Move the promotion onto that same deferred path. layout() now performs only a read-only candidate check and, when a promotion is genuinely pending, schedules promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded() for the next runloop tick via a debounced DispatchWorkItem. The hierarchy mutation therefore never runs inside layout(). The deferred work re-validates all state (!isHostedInspectorSideDockActive(), slot view, candidate) before mutating, so it is safe even if the layout changes in between, and the single-pending-work-item guard prevents scheduling churn (layout() runs frequently).

Scope / tradeoffs

Testing

This crash is layout-timing-dependent and intermittent, and HostContainerView is a private nested AppKit view class with no public seam, so there is no clean deterministic unit test for it (stated per the repo regression-test policy). Verification is by code reasoning: the change removes the only synchronous hierarchy mutation reachable from layout(), matching the documented crash backtrace, and reuses the existing, already-shipping deferred-promotion pattern.

Closes #6150


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


Summary by cubic

Fixes the vertical-sidebar tab-switch crash by deferring hosted‑inspector side‑dock promotion out of layout, and adds a per‑pane runaway‑memory guardrail with a banner, an orange sidebar badge, and a confirmable kill action. Addresses #6150 and keeps the UI responsive during tab switches.

  • Bug Fixes

    • Defers hosted‑inspector side‑dock promotion to the next runloop tick, preventing EXC_BAD_ACCESS/EXC_BREAKPOINT during tab switches (Tab-switch crash in vertical sidebar on macOS 14.8.2 — objc use-after-free via AppKit, likely same root cause as #653 #6150); re‑checks dock state to preserve adaptive bottom‑dock and cancels stale tasks.
    • Aligns guardrail settings decoding so the enable toggle and threshold load and persist correctly.
    • Preserves db client test mock exports by adding closeCloudDbForTests in the web test mocks to keep the test runner stable.
    • Guardrail scans panes via all live tab managers (registered window contexts and recoverable routes) so warnings cover panes in every window.
    • Scopes the memory‑warning banner to the owning window/tab manager so it only appears where the workspace lives.
  • New Features

    • Per‑pane memory guardrail: background scan (~4s), TTY‑based attribution, hysteresis with edge‑triggered banner, and an orange sidebar badge; banner shows pane/workspace, current memory, and foreground command; “Kill Pane Process” sends SIGTERM → SIGKILL to high‑memory process groups with revalidation and stale‑task cancellation, and falls back to closing the pane if no safe target exists.
    • Settings/search/localization: enable toggle and “Memory Warning Threshold (GB)”, search anchors, localized strings; starts at app launch and updates the sidebar badge via the notification store. TerminalSurface.foregroundProcessID() and .controllingTTYName() added via GhosttyKit. Tests cover thresholding, hysteresis, dismissal, multi‑pane, TTY/no‑TTY, process‑group selection, window scoping, and manager routing.

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

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Added per-pane “Runaway Memory Guardrail” with enable/disable (default: enabled) and a warning threshold (default: 8 GB).
  • UI/UX

    • Added an always-available, top dismissible warning banner with localized pane/workspace details and “Kill Pane Process” (with confirmation).
    • Added an orange sidebar warning badge and tooltip for workspaces with active memory warnings.
  • Settings / Localizations

    • Updated terminal settings with guardrail controls and added localized strings for the banner, dialogs, and tooltips (including accessibility text).
  • Tests

    • Added unit tests for thresholding, hysteresis, dismissal, multi-pane behavior, and TTY-based attribution.

austinywang and others added 4 commits June 17, 2026 13:03
A single pane running a leaking process (e.g. uv run pytest growing to
~14 GB RSS) makes macOS aggregate the child memory under the app, report
hundreds of GB, declare "out of application memory", and OOM-suspend the
whole app — killing every other healthy pane with no prior signal.

This adds a per-pane guardrail that catches a runaway tree at the pane
level first:

- A background timer (PaneMemoryGuardrail) polls every live pane ~every
  4s. It attributes process-tree memory by the pane's controlling tty:
  every process under the pane (shell + descendants + background jobs)
  shares the tty, so it sums physical-footprint bytes across all pids on
  that tty device via the existing CmuxTopProcessSnapshot libproc walk.
- When a pane crosses a configurable threshold (default 8 GB) it
  edge-triggers an orange warning badge on the workspace tab and a
  dismissible banner identifying the pane, its process-tree memory, and
  the foreground command. Hysteresis clears at 0.8x threshold; the banner
  fires once per crossing and re-arms after it clears.
- The banner's "Kill Pane Process" action (with confirm) sends SIGTERM
  then SIGKILL to the pane's foreground process group, leaving the shell
  alive; falls back to closing the pane when there is no foreground group.
- New Terminal settings: enable toggle + threshold (GB), default on / 8 GB.
- Below threshold it stays completely silent (no always-on memory UI).

Ghostty foreground-pid / tty-name accessors added on TerminalSurface.
Pure edge-trigger engine unit-tested (PaneMemoryGuardrailTests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rdrail

# Conflicts:
#	Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+ProcessInfo.swift
#	Resources/Localizable.xcstrings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The browser HostContainerView.layout() override synchronously called
promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded(), which mutates
the view hierarchy (addSubview / removeFromSuperview + NSLayoutConstraint
activation) when a docked DevTools/inspector split is detected. Mutating
the view hierarchy synchronously inside an AppKit layout pass re-enters
the layout machinery and crashes:

- EXC_BREAKPOINT via -[NSWindow _postWindowNeedsUpdateConstraints]
  (Auto Layout constraint recursion) — the #653 ASI backtrace, whose
  faulting frames are ___NSViewLayout_block_invoke -> ... -> addSubview:.
- EXC_BAD_ACCESS in objc_msgSend called from ___NSViewLayout_block_invoke
  (a view freed mid-layout and then messaged) — the #6150 group A/B shape
  on macOS 14.8.2, triggered by switching to a tab with a browser surface.

The dock-config path already defers its identical hierarchy mutation via
scheduleHostedInspectorDockConfigurationSync (DispatchQueue.main.async),
and viewDidMoveToWindow/Superview promote via the deferred
scheduleHostedInspectorDividerReapply work item. Only the layout()
override mutated synchronously. This change moves the promotion onto the
same deferred path: layout() now performs a read-only candidate check and
schedules the promotion for the next runloop tick, so the hierarchy
mutation never runs inside layout(). The deferred work re-validates all
state before mutating, so it is safe if the layout changes in between.

Closes #6150

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 19, 2026 2:59am
cmux-staging Building Building Preview, Comment Jun 19, 2026 2:59am

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

Adds a per-pane "Runaway Memory Guardrail" that periodically samples process-tree resident memory by controlling TTY, edge-triggers a dismissible banner and sidebar badge when a configurable GB threshold is crossed, and exposes kill/dismiss actions with hysteresis. Also defers HostContainerView's side-dock promotion out of the synchronous layout() pass to prevent in-layout view-hierarchy mutations.

Changes

Pane Runaway Memory Guardrail

Layer / File(s) Summary
Settings defaults keys
Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/TerminalCatalogSection.swift
Adds runawayMemoryGuardrailEnabled (Bool, default true) and runawayMemoryGuardrailThresholdGB (Double, default 8) as new DefaultsKey properties.
TerminalSurface process info helpers
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+ProcessInfo.swift
New @MainActor extension adds foregroundProcessID() and controllingTTYName() reading from libghostty, with nil guards for absent/empty/non-positive values.
Test stubs for libghostty surface functions
Packages/macOS/CmuxTerminal/Tests/GhosttyRuntimeTestStubs/...
Adds ghostty_string_s struct and stub implementations for ghostty_surface_foreground_pid() and ghostty_surface_tty_name() in test infrastructure.
Guardrail data types, engine, and process killer
Sources/PaneMemoryPaneKey.swift, Sources/PaneMemoryDescriptor.swift, Sources/PaneMemorySample.swift, Sources/PaneMemoryWarning.swift, Sources/PaneMemoryGuardrailEngine.swift, Sources/PaneMemoryGuardrailEngineOutput.swift, Sources/PaneMemoryProcessKiller.swift
Defines pane-identification, descriptor, sample, and warning value types. Adds PaneMemoryGuardrailEngine with edge-triggered threshold detection and hysteresis clearing. Adds PaneMemoryProcessKiller sending SIGTERM then deferred SIGKILL with grace-period timer.
PaneMemoryGuardrail ObservableObject — sampling and banner lifecycle
Sources/PaneMemoryGuardrail.swift
Main-actor singleton with background DispatchSourceTimer; per-tick reads descriptors, computes off-main TTY-indexed memory samples via CmuxTopProcessSnapshot, applies engine, manages activeBanner lifecycle, emits warned-workspace badge updates, and provides banner dismiss/kill actions.
PaneMemoryGuardrailBanner SwiftUI view
Sources/PaneMemoryGuardrailBannerView.swift
Dismissible top-of-content banner with formatted memory text, pane/workspace location, optional command name, kill-with-confirmation flow, and dismiss action.
SidebarUnreadModel + AppDelegate startup wiring
Sources/TerminalNotificationStore.swift, Sources/AppDelegate.swift
SidebarUnreadModel gains memoryWarningWorkspaceIds set with set/query APIs. AppDelegate wires PaneMemoryGuardrail.shared with a pane enumerator, workspace-badge callback, and pane-close callback, then starts the guardrail.
ContentView overlay, sidebar badge, and tab integration
Sources/ContentView.swift
Overlays PaneMemoryGuardrailBanner on terminal content; computes per-workspace hasMemoryWarning and passes into TabItemView, which conditionally renders an orange warning-triangle badge with tooltip and accessibility label.
Terminal settings UI and localization
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift, Resources/Localizable.xcstrings
Adds guardrail toggle row (dynamic subtitle) and threshold stepper (disabled when off) to terminal settings with search anchors. Includes two curated setting entries. Adds en/ja localized strings for banner, kill dialog, sidebar tooltip, and settings UI.
Unit tests and Xcode project wiring
cmuxTests/PaneMemoryGuardrailTests.swift, cmux.xcodeproj/project.pbxproj, Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsRowAnchorResolutionTests.swift
Tests cover threshold detection, dismissal/hysteresis, pane-disappear cleanup, simultaneous multi-pane crossing, and tty-based memory summation. Build phases updated to compile three new Swift files. Settings test updated with new anchor entries.

BrowserPanelView Deferred Side-Dock Promotion Fix

Layer / File(s) Summary
Deferred side-dock promotion
Sources/Panels/BrowserPanelView.swift
HostContainerView adds a Task<Void, Never>? and UUID, cancels the task in deinit, and replaces the direct in-layout promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded() call with scheduleHostedInspectorSideDockPromotionIfNeeded(), which defers the mutation to the next runloop tick to prevent view-hierarchy changes inside the synchronous layout pass (addressing issue #6150).

Sequence Diagram(s)

sequenceDiagram
  participant Timer as Background Timer
  participant Guardrail as PaneMemoryGuardrail
  participant Snapshot as CmuxTopProcessSnapshot
  participant Engine as PaneMemoryGuardrailEngine
  participant Banner as PaneMemoryGuardrailBanner
  participant Sidebar as TabItemView / SidebarUnreadModel

  Timer->>Guardrail: tick (dispatched to MainActor)
  Guardrail->>Guardrail: resolve enabled/thresholdBytes from defaults
  Guardrail->>Guardrail: paneProvider() → [PaneMemoryDescriptor]
  Guardrail->>Snapshot: computeSamples(descriptors) off-main
  Snapshot-->>Guardrail: [PaneMemorySample] (TTY-indexed memory sums)
  Guardrail->>Engine: ingest(samples, thresholdBytes)
  Engine-->>Guardrail: Output(bannerToPresent, warnedWorkspaceIds, clearedPanes)
  Guardrail->>Banner: activeBanner = PaneMemoryWarning
  Guardrail->>Sidebar: onWarnedWorkspacesChanged(Set<UUID>)
  Sidebar->>Sidebar: setMemoryWarningWorkspaceIds → orange badge shown

  Note over Banner: User taps "Kill"
  Banner->>Guardrail: killActivePaneProcess()
  Guardrail->>Guardrail: PaneMemoryProcessKiller.terminate(pgids)
  Guardrail->>Engine: acknowledgeHandled(paneKey)

  Note over Banner: User taps "Dismiss"
  Banner->>Guardrail: dismissActiveBanner()
  Guardrail->>Engine: dismiss(paneKey)
  Guardrail->>Banner: activeBanner = nil
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#5859: The main PR extends SidebarUnreadModel introduced in #5859 by adding memoryWarningWorkspaceIds/hasMemoryWarning(forWorkspaceId:) and wiring ContentView to render the memory-warning badge.
  • manaflow-ai/cmux#5012: Both PRs add new terminal settings that participate in the settings search/anchor system, with overlapping integration into SettingsRowAnchorResolutionTests and CuratedSettingEntry infrastructure.

Suggested reviewers

  • lawrencecchen

Poem

🐇 Hop hop, a watchful ear,
Memory creeps — the guardrail's here!
Above eight gigs? A banner flies,
An orange badge adorns the skies.
SIGTERM sent, then SIGKILL too —
No runaway pane gets past this crew! 🚨


Important

Pre-merge checks failed

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

❌ Failed checks (4 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Concurrency ❌ Error Introduces completion-handler API with @escaping closure in new internal cmux code: validateBeforeSIGKILL parameter in PaneMemoryProcessKiller.terminate() can be expressed as async function. Replace @escaping @Sendable () -> Set with async function parameter or make terminate() async with validateBeforeSIGKILL: @Sendable () async -> Set.
Cmux Full Internationalization ❌ Error The PR adds 18 new user-facing strings to Resources/Localizable.xcstrings with only 2 locales (en, ja), but the catalog already supports 20 locales including ar, bs, da, de, es, fr, it, km, ko, nb,... Add translations for all 20 supported locales to: paneMemoryGuardrail.* (11 keys), sidebar.memoryWarning.* (2 keys), and settings.terminal.memoryGuardrail.* (5 keys).
Cmux Swiftui State Layout ❌ Error PR adds new @Published property (memoryWarningWorkspaceIds) to SidebarUnreadModel (ObservableObject), violating the rule requiring @Observable for new cmux-owned state. Migrate SidebarUnreadModel from ObservableObject+@published to @Observable+@State or refactor to use value snapshots in views, per swiftui-state-layout.md rule for legacy state migration.
Cmux Architecture Rethink ❌ Error PR introduces polling (DispatchSourceTimer every 4s), delayed dispatch for deferred work, and split state ownership: PaneMemoryGuardrail owns warned-pane engine state while SidebarUnreadModel owns... Consolidate memory-warning state into single owner: either compute badge state on-demand from guardrail.warnedWorkspaceIds() or move all warned-pane logic into notification store with guardrail as transport-only callback, eliminating the...
Docstring Coverage ⚠️ Warning Docstring coverage is 19.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main bug fix (#6150 tab-switch crash) and the specific solution (deferring promotion out of layout), making it clear and specific.
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 introduces proper actor isolation: PaneMemoryGuardrail is explicitly @MainActor with @ObservationIgnored on mutable state; all callbacks are @MainActor-annotated; value types are Sendable; Sideb...
Cmux Swift Blocking Runtime ✅ Passed PR introduces background polling timer (4s) for memory guardrail but contains no blocking primitives: no semaphores, blocking waits, Task.sleep, main-queue sync, or manual locks. Async off-thread c...
Cmux Expensive Synchronous Load ✅ Passed PR adds no expensive synchronous operations to main actor or interactive paths. Memory scan work (CmuxTopProcessSnapshot.capture) explicitly runs off-main via Task.detached; main-thread paneProvide...
Cmux Cache Substitution Correctness ✅ Passed The PR's memory guardrail feature uses transient (non-persisted) UI state only. Memory warning state is never saved to disk, included in snapshots, or used in undo/history paths. The cached snapsho...
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift files, C test stubs, config files, and localization strings. The "cmux no hacky sleeps" check explicitly excludes Swift code (covered by swift-blocking-runtime.md) and only a...
Cmux Algorithmic Complexity ✅ Passed All snapshot lookups use O(1) dictionary access; paneProvider nested loop (O(W*P)~5k ops) runs every 4 seconds off-main; memoryPressureProcessGroupIDs and engine.ingest are linear/n-log-n on bounde...
Cmux Swift @Concurrent ✅ Passed Swift concurrent annotations are correct: file-heavy work explicitly hops off MainActor via Task.detached(), nonisolated static functions don't need @concurrent, private async functions are properl...
Cmux Swift File And Package Boundaries ✅ Passed PR respects Swift file/package boundaries: new files (67-351 lines) have clear single responsibilities with pure logic extracted (Engine, ProcessKiller) and fully tested; additions to oversized fil...
Cmux Swiftpm Lockfiles ✅ Passed PR does not modify Package.swift, Package.resolved, or package .gitignore files; only adds source file references to cmux.xcodeproj/project.pbxproj, which is not a SwiftPM dependency change.
Cmux Swift Logging ✅ Passed All new production Swift files comply with swift-logging.md: no print/debugPrint/dump/NSLog calls, no ad hoc logging, no Logger issues, and banner displays only safe user-visible data.
Cmux User-Facing Error Privacy ✅ Passed All user-facing strings comply with privacy rules: no vendor names, provider flags, credentials, environment variables, database details, or raw error messages. Strings use product-safe terms ("run...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not introduce new standalone windows/panels. PaneMemoryGuardrailBanner is a SwiftUI View overlay, not NSWindow/NSPanel/NSWindowController. Lint script passed with 30 registered identifiers.
Cmux Source Artifacts ✅ Passed All 23 changed files are legitimate hand-written source, test, config, or localization resources. No build artifacts, generated logs, caches, DerivedData, temp directories, or forbidden scratch pat...
Description check ✅ Passed The PR description is comprehensive and well-structured, covering root cause, fix, scope, testing, and closure of related issues.
✨ 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-6150

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.

# Conflicts:
#	.github/swift-file-length-budget.tsv
@greptile-apps

greptile-apps Bot commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR contains two changes: a targeted one-line crashfix to BrowserPanelView that defers hosted-inspector side-dock promotion out of AppKit's layout() pass, and a new per-pane runaway-memory guardrail feature (background TTY-based scan, dismissible banner, orange sidebar badge, and a SIGTERM→SIGKILL kill action).

  • Crashfix (BrowserPanelView): layout() no longer calls promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded() synchronously; it instead schedules a Task { @MainActor } with Task.yield() so the hierarchy mutation (addSubview + NSLayoutConstraint.activate) always runs after the layout pass. A UUID-based guard prevents stale task execution, and shouldForceHostedInspectorBottomDock is re-checked inside the promote function to preserve adaptive bottom-dock behaviour.
  • Memory guardrail: PaneMemoryGuardrail (@Observable @MainActor) drives a 4-second DispatchSourceTimer that enqueues Task { @MainActor in tick() } each cycle. Sampling runs Task.detached(priority: .utility) using CmuxTopProcessSnapshot for TTY-based attribution. PaneMemoryGuardrailEngine isolates the edge-trigger/hysteresis logic as a pure struct for unit testing. The SIGTERM→SIGKILL escalation in PaneMemoryProcessKiller uses a cancellable DispatchSourceTimer-backed async wait.

Confidence Score: 5/5

Safe to merge. The BrowserPanelView crashfix is minimal and well-contained; the memory guardrail is correctly structured with @observable, off-main sampling, and tested edge-trigger/hysteresis logic.

The BrowserPanelView change removes the only synchronous view-hierarchy mutation in the layout path (confirmed by backtrace match) and reuses the already-shipping deferred-promotion pattern. The memory guardrail follows established cmux patterns: @observable for the model, Task.detached for background work, and a pure struct engine for testable decision logic. Previous review findings (ObservableObject→@observable, asyncAfter→cancellable Task, DispatchQueue.global→Task.detached) were all addressed. The two findings here are non-blocking style observations.

No files require special attention. The two comments are non-blocking observations on copy style and task-lifecycle explicitness.

Important Files Changed

Filename Overview
Sources/Panels/BrowserPanelView.swift Crashfix: defers side-dock promotion out of layout() via Task.yield(); adds shouldForceHostedInspectorBottomDock pre-check inside the promote function; deinit now cancels the promotion task. Logic is clean.
Sources/PaneMemoryGuardrail.swift Uses @observable correctly (fixed from prior review), Task.detached for off-main scanning, DispatchSourceTimer for polling. applySamples clears isScanning and scanApplyTask before dispatching badge/banner updates — sequencing is fine on MainActor.
Sources/PaneMemoryGuardrailBannerView.swift Clean SwiftUI view reading @observable guardrail singleton; UUID-guarded kill confirmation; window-scoping via TabManager.ownsPaneMemoryGuardrailWarning; all strings localized with catalog keys.
Sources/PaneMemoryProcessKiller.swift Cancellable SIGTERM→SIGKILL escalation using DispatchSourceTimer-backed withCheckedContinuation; validateBeforeSIGKILL re-checks live snapshot before force-kill. Confirmation dialog message contains OS signal names as user-facing copy.
Sources/PaneMemoryGuardrailEngine.swift Pure struct edge-trigger + hysteresis logic; expired panes removed via formIntersection on live keys; well-covered by unit tests.
Sources/AppDelegate+PaneMemoryGuardrail.swift Collects pane descriptors from all window contexts (mainWindowContexts, recoverableMainWindowRoutes, bootstrap tabManager) with dedup via ObjectIdentifier; clean weak-capture chains to notificationStore and AppDelegate.
Sources/TerminalNotificationStore.swift Adds memoryWarningWorkspaceIds to existing ObservableObject with manual objectWillChange.send() — correct pattern for non-@published fields; change-guarded to prevent spurious notifications.
Resources/Localizable.xcstrings All 18 new string keys include both en and ja translations; positional format specifiers (%1$@, %2$@, %3$@) used correctly for Japanese word-order flexibility.
cmuxTests/PaneMemoryGuardrailTests.swift Good coverage of engine thresholding, hysteresis, dismissal, multi-pane, TTY/no-TTY attribution, process-group selection, window scoping, and manager routing. Correct use of Swift Testing framework.
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+ProcessInfo.swift Clean @mainactor extension exposing foregroundProcessID() and controllingTTYName() via ghostty_surface_foreground_pid / ghostty_surface_tty_name; correctly frees the exported string with defer.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Timer as DispatchSourceTimer<br/>(timerQueue)
    participant MA as MainActor<br/>PaneMemoryGuardrail
    participant Det as Task.detached<br/>(utility)
    participant UI as SwiftUI<br/>Banner/Badge

    Timer->>MA: "Task { @MainActor in tick() }"
    MA->>MA: paneProvider() → [PaneMemoryDescriptor]
    MA->>Det: computeCachedSamples(descriptors:)
    Det-->>MA: [PaneMemorySample]
    MA->>MA: engine.ingest(samples:) → output
    MA->>UI: "activeBanner = warning (edge-trigger)"
    MA->>UI: onWarnedWorkspacesChanged(ids) → badge

    Note over UI: User clicks Kill
    UI->>MA: killPaneProcess(for:)
    MA->>Det: computeFreshSamples([descriptor])
    Det-->>MA: PaneMemorySample
    MA->>MA: finishKillActivePaneProcess
    MA->>Det: PaneMemoryProcessKiller.terminate(pgids:)
    Det->>Det: kill(-pgid, SIGTERM)
    Det->>Det: waitForGracePeriod(3s)
    Det->>Det: validateBeforeSIGKILL()
    Det->>Det: kill(-pgid, SIGKILL) if still over threshold
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 Timer as DispatchSourceTimer<br/>(timerQueue)
    participant MA as MainActor<br/>PaneMemoryGuardrail
    participant Det as Task.detached<br/>(utility)
    participant UI as SwiftUI<br/>Banner/Badge

    Timer->>MA: "Task { @MainActor in tick() }"
    MA->>MA: paneProvider() → [PaneMemoryDescriptor]
    MA->>Det: computeCachedSamples(descriptors:)
    Det-->>MA: [PaneMemorySample]
    MA->>MA: engine.ingest(samples:) → output
    MA->>UI: "activeBanner = warning (edge-trigger)"
    MA->>UI: onWarnedWorkspacesChanged(ids) → badge

    Note over UI: User clicks Kill
    UI->>MA: killPaneProcess(for:)
    MA->>Det: computeFreshSamples([descriptor])
    Det-->>MA: PaneMemorySample
    MA->>MA: finishKillActivePaneProcess
    MA->>Det: PaneMemoryProcessKiller.terminate(pgids:)
    Det->>Det: kill(-pgid, SIGTERM)
    Det->>Det: waitForGracePeriod(3s)
    Det->>Det: validateBeforeSIGKILL()
    Det->>Det: kill(-pgid, SIGKILL) if still over threshold
Loading

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

Comment thread Sources/PaneMemoryGuardrail.swift Outdated
Comment thread Sources/PaneMemoryGuardrail.swift Outdated
Comment thread Sources/PaneMemoryGuardrail.swift Outdated

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

🤖 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
`@Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface`+ProcessInfo.swift:
- Around line 33-35: The String initialization for decoding the TTY name data
uses lossy decoding which can silently substitute invalid UTF-8 bytes with
replacement characters, potentially creating incorrect attribution identifiers.
Replace the `String(decoding: data, as: UTF8.self)` call with a strict UTF-8
validation method such as `String(validatingUTF8:)` that returns nil when
encountering invalid UTF-8 sequences, ensuring the function fails closed by
returning nil for corrupted data rather than producing a potentially wrong TTY
identifier.

In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 6367-6379: The order of operations in the layout pass is
incorrect: scheduleHostedInspectorSideDockPromotionIfNeeded is queued before
enforceAdaptiveBottomDockIfNeeded can reject it and enforce bottom docking
instead. Reorder the code so that enforceAdaptiveBottomDockIfNeeded is called
first to determine if bottom docking should be enforced, and only then call
scheduleHostedInspectorSideDockPromotionIfNeeded if the adaptive guard permits
side-dock promotion. This prevents the queued promotion from running on the next
tick when the adaptive guard has decided to use bottom docking instead.

In `@Sources/PaneMemoryGuardrail.swift`:
- Around line 326-334: The killActivePaneProcess() function uses stale cached
data from lastSamplesByKey to retrieve foregroundProcessGroupIDs for
termination, but processes can change between sampling and the kill action.
Replace the cached lookup of foregroundProcessGroupIDs from lastSamplesByKey
with a fresh resolution of the active pane's current process context at action
time. Ensure that PaneMemoryProcessKiller.terminate() is called with the current
authoritative foregroundProcessGroupIDs obtained from a real-time query rather
than the previously cached values.
- Around line 296-310: The activeBanner is not being cleared when its
corresponding pane disappears from the live samples in lastSamplesByKey, even if
the pane isn't explicitly in output.clearedPanes. Add a check in the if block
where activeBanner is processed to also set activeBanner to nil when
lastSamplesByKey[activeKey] becomes nil, in addition to the existing check for
output.clearedPanes.contains(activeKey). This ensures stale banners are removed
from the screen when their pane closes and allows new bannerToPresent events to
display.
🪄 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: cb76dd65-7cb8-4715-b8fc-70d95723a1cc

📥 Commits

Reviewing files that changed from the base of the PR and between 936d8db and e53067d.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (12)
  • Packages/macOS/CmuxSettings/Sources/CmuxSettings/Keys/TerminalCatalogSection.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+ProcessInfo.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/PaneMemoryGuardrail.swift
  • Sources/PaneMemoryGuardrailBannerView.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/TerminalNotificationStore.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/PaneMemoryGuardrailTests.swift

Comment thread Sources/Panels/BrowserPanelView.swift Outdated
Comment thread Sources/PaneMemoryGuardrail.swift Outdated
Comment thread Sources/PaneMemoryGuardrail.swift Outdated

@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

🤖 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
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift`:
- Around line 61-73: The startObservingSettings() function is missing the newly
added guardrail settings models from its observation list. Add
memGuardrailEnabled and memGuardrailThresholdGB to the models array in the
startObservingSettings function so they are included when startObserving() is
called on each model, ensuring these controls stay synchronized with settings
changes from other surfaces.

In `@Resources/Localizable.xcstrings`:
- Around line 170073-170196: The command.terminalClearScreenKeepScrollback.title
key has multiple locale entries marked as "needs_review" that contain only the
English source string as placeholders instead of actual translations. Replace
each "needs_review" placeholder entry for locales ar, bs, da, de, es, fr, it,
km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant with genuine
translations of the phrase in their respective languages, and change the state
from "needs_review" to "translated" for each entry. Ensure every supported
locale in this key has a properly translated value or defer adding this key
until translations are available.
- Around line 170448-170571: The shortcut.clearScreenKeepScrollback.label entry
contains multiple locales (ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR,
ru, th, tr, uk, zh-Hans, zh-Hant) marked with "needs_review" state but using
English placeholder text instead of actual translations. Either provide genuine
translated values for each locale marked as "needs_review" or remove the entire
shortcut.clearScreenKeepScreenback.label key from the catalog until proper
translations for all supported locales are available. Ensure that all
user-facing strings in production have real localized content for every
supported locale in the xcstrings file.
🪄 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: 35e73f75-0773-4bb9-bd8f-9928db542d1a

📥 Commits

Reviewing files that changed from the base of the PR and between e53067d and 51da1aa.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (7)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/TerminalNotificationStore.swift
  • cmux.xcodeproj/project.pbxproj

@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

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 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
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift`:
- Around line 61-73: The startObservingSettings() function is missing the newly
added guardrail settings models from its observation list. Add
memGuardrailEnabled and memGuardrailThresholdGB to the models array in the
startObservingSettings function so they are included when startObserving() is
called on each model, ensuring these controls stay synchronized with settings
changes from other surfaces.

In `@Resources/Localizable.xcstrings`:
- Around line 170073-170196: The command.terminalClearScreenKeepScrollback.title
key has multiple locale entries marked as "needs_review" that contain only the
English source string as placeholders instead of actual translations. Replace
each "needs_review" placeholder entry for locales ar, bs, da, de, es, fr, it,
km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant with genuine
translations of the phrase in their respective languages, and change the state
from "needs_review" to "translated" for each entry. Ensure every supported
locale in this key has a properly translated value or defer adding this key
until translations are available.
- Around line 170448-170571: The shortcut.clearScreenKeepScrollback.label entry
contains multiple locales (ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR,
ru, th, tr, uk, zh-Hans, zh-Hant) marked with "needs_review" state but using
English placeholder text instead of actual translations. Either provide genuine
translated values for each locale marked as "needs_review" or remove the entire
shortcut.clearScreenKeepScreenback.label key from the catalog until proper
translations for all supported locales are available. Ensure that all
user-facing strings in production have real localized content for every
supported locale in the xcstrings file.
🪄 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: 35e73f75-0773-4bb9-bd8f-9928db542d1a

📥 Commits

Reviewing files that changed from the base of the PR and between e53067d and 51da1aa.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (7)
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/TerminalNotificationStore.swift
  • cmux.xcodeproj/project.pbxproj
🛑 Comments failed to post (3)
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift (1)

61-73: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing observers for the newly added guardrail settings models.

startObservingSettings() does not include memGuardrailEnabled or memGuardrailThresholdGB. As a result, the new controls can go stale if settings are changed from another surface while this view is mounted (Line 61-73).

Proposed fix
 private func startObservingSettings() {
     let models: [any SettingObservationStarting] = [
         scrollBar,
         copyOnSelect,
         autoResume,
         hibernation,
         idleSeconds,
         maxLive,
         rendererReclaim,
         rendererIdleSeconds,
         rendererMaxWarm,
+        memGuardrailEnabled,
+        memGuardrailThresholdGB,
     ]
     models.forEach { $0.startObserving() }
 }
🤖 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
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/TerminalSection.swift`
around lines 61 - 73, The startObservingSettings() function is missing the newly
added guardrail settings models from its observation list. Add
memGuardrailEnabled and memGuardrailThresholdGB to the models array in the
startObservingSettings function so they are included when startObserving() is
called on each model, ensuring these controls stay synchronized with settings
changes from other surfaces.
Resources/Localizable.xcstrings (2)

170073-170196: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ship real translations instead of needs_review placeholders.

This key leaves every non-English locale on the English source string, so the command title is effectively untranslated in production. Please replace the placeholders with actual translations for every supported locale, or defer adding the key until translations are ready. As per coding guidelines, all user-facing strings must be localized and every supported locale in the touched catalog needs translated entries.

🤖 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 `@Resources/Localizable.xcstrings` around lines 170073 - 170196, The
command.terminalClearScreenKeepScrollback.title key has multiple locale entries
marked as "needs_review" that contain only the English source string as
placeholders instead of actual translations. Replace each "needs_review"
placeholder entry for locales ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR,
ru, th, tr, uk, zh-Hans, and zh-Hant with genuine translations of the phrase in
their respective languages, and change the state from "needs_review" to
"translated" for each entry. Ensure every supported locale in this key has a
properly translated value or defer adding this key until translations are
available.

Source: Coding guidelines


170448-170571: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ship real translations instead of needs_review placeholders.

This shortcut label has the same problem: all non-English locales still resolve to the English text, which violates the full-internationalization rule for production UI strings. Please provide translated values for every supported locale in the catalog, or hold the key until they’re available. As per coding guidelines, all user-facing strings must be localized and every supported locale in the touched catalog needs translated entries.

🤖 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 `@Resources/Localizable.xcstrings` around lines 170448 - 170571, The
shortcut.clearScreenKeepScrollback.label entry contains multiple locales (ar,
bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant)
marked with "needs_review" state but using English placeholder text instead of
actual translations. Either provide genuine translated values for each locale
marked as "needs_review" or remove the entire
shortcut.clearScreenKeepScreenback.label key from the catalog until proper
translations for all supported locales are available. Ensure that all
user-facing strings in production have real localized content for every
supported locale in the xcstrings file.

Source: Coding guidelines

@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

Caution

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

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

6178-6181: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-run the adaptive guard inside the deferred promotion.

A promotion can be queued while side-dock is allowed, then a later layout can hit Line 6389 and request bottom docking before the queued block runs. The block at Line 6181 still promotes without re-checking that adaptive state, so a stale next-tick item can override the bottom-dock decision.

Proposed fix
             let workItem = DispatchWorkItem { [weak self] in
                 guard let self else { return }
                 self.hostedInspectorSideDockPromotionWorkItem = nil
+                guard !self.enforceAdaptiveBottomDockIfNeeded(reason: "host.sideDockPromotion") else {
+                    return
+                }
                 _ = self.promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded()
             }

Also applies to: 6389-6395

🤖 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/BrowserPanelView.swift` around lines 6178 - 6181, The
DispatchWorkItem block in the hostedInspectorSideDockPromotionWorkItem deferred
promotion logic does not re-check the adaptive guard condition before executing
the promotion. When the work item eventually runs, layout state may have changed
to request bottom docking instead of side-dock, but the queued promotion
proceeds anyway, overriding the more recent decision. Before calling
promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded() inside the work item,
add back the same adaptive guard condition that was checked when the work item
was originally queued to ensure the promotion only happens if side-dock is still
the appropriate state.
Sources/PaneMemoryGuardrailBannerView.swift (1)

19-29: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the kill confirmation when the active warning changes.

The dialog action kills whatever guardrail.activeBanner is current at confirmation time. If pane A clears and pane B is presented while the dialog is open, confirming can target B without the user opening B’s confirmation.

🛡️ Proposed fix
         }
         .animation(.easeInOut(duration: 0.2), value: guardrail.activeBanner)
+        .onChange(of: guardrail.activeBanner?.key) { _ in
+            isConfirmingKill = false
+        }
     }

Also applies to: 67-76

🤖 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/PaneMemoryGuardrailBannerView.swift` around lines 19 - 29, When the
active banner changes, the kill confirmation dialog may still be open and could
target the wrong pane. Add an onChange modifier to the body view that watches
for changes to guardrail.activeBanner and resets the confirmation dialog state
(dismiss or set to nil/false) whenever the active banner changes. This ensures
that if a user has a confirmation dialog open and the banner switches while the
dialog is displayed, the dialog will be dismissed rather than potentially
confirming an action on the wrong banner. Apply the same fix to the other
location mentioned at lines 67-76.
Sources/PaneMemoryGuardrail.swift (1)

192-195: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp invalid threshold values before converting to Int64.

UserDefaults is user/config writable, so NaN, infinity, or a huge finite value can survive max(...) and trap on Int64(...) during the polling tick. Fall back to the default when the configured value is non-finite or unrepresentable.

🛡️ Proposed fix
     private static let pollInterval: TimeInterval = 4
     private static let defaultThresholdGB: Double = 8
     private static let minThresholdGB: Double = 1
+    private static let bytesPerGB = 1024.0 * 1024.0 * 1024.0
@@
     private func thresholdBytes() -> Int64 {
         let configured = UserDefaults.standard.object(forKey: DefaultsKeys.thresholdGB) as? Double
             ?? Self.defaultThresholdGB
-        let gb = max(Self.minThresholdGB, configured)
-        return Int64(gb * 1024 * 1024 * 1024)
+        let gb = configured.isFinite
+            ? max(Self.minThresholdGB, configured)
+            : Self.defaultThresholdGB
+        let bytes = gb * Self.bytesPerGB
+        guard bytes.isFinite, bytes < Double(Int64.max) else {
+            return Int64(Self.defaultThresholdGB * Self.bytesPerGB)
+        }
+        return Int64(bytes)
     }

Also applies to: 249-253

🤖 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/PaneMemoryGuardrail.swift` around lines 192 - 195, The threshold
values read from UserDefaults can contain invalid values such as NaN, infinity,
or unreasonably large numbers that will trap when converting to Int64. In the
code around lines 249-253 where the threshold is retrieved from UserDefaults and
converted to Int64, add validation to ensure the value is finite (check that it
is neither NaN nor infinite) and representable as an Int64 before performing the
conversion. If the value is invalid or non-finite, fall back to using
defaultThresholdGB instead. This ensures robustness against malformed
configuration values.
🤖 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/PaneMemoryGuardrail.swift`:
- Around line 420-442: The finishKillActivePaneProcess method currently closes
the pane whenever processGroupIDs is empty after filtering, but this doesn't
distinguish between having no runaway processes versus memory already being
cleared to a healthy state. Modify the logic to check if memory pressure still
exists at the kill threshold in the sample data. Only call onRequestClosePane
when processGroupIDs is empty AND the sample indicates memory is still above the
clear threshold. If the sample shows memory has already been cleared (below the
threshold), return early without closing the pane. This requires passing the
full sample result to finishKillActivePaneProcess instead of just the
processGroupIDs array, so you can check both the current memory state and
process information before deciding whether to close.

---

Outside diff comments:
In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 6178-6181: The DispatchWorkItem block in the
hostedInspectorSideDockPromotionWorkItem deferred promotion logic does not
re-check the adaptive guard condition before executing the promotion. When the
work item eventually runs, layout state may have changed to request bottom
docking instead of side-dock, but the queued promotion proceeds anyway,
overriding the more recent decision. Before calling
promoteHostedInspectorSideDockFromCurrentLayoutIfNeeded() inside the work item,
add back the same adaptive guard condition that was checked when the work item
was originally queued to ensure the promotion only happens if side-dock is still
the appropriate state.

In `@Sources/PaneMemoryGuardrail.swift`:
- Around line 192-195: The threshold values read from UserDefaults can contain
invalid values such as NaN, infinity, or unreasonably large numbers that will
trap when converting to Int64. In the code around lines 249-253 where the
threshold is retrieved from UserDefaults and converted to Int64, add validation
to ensure the value is finite (check that it is neither NaN nor infinite) and
representable as an Int64 before performing the conversion. If the value is
invalid or non-finite, fall back to using defaultThresholdGB instead. This
ensures robustness against malformed configuration values.

In `@Sources/PaneMemoryGuardrailBannerView.swift`:
- Around line 19-29: When the active banner changes, the kill confirmation
dialog may still be open and could target the wrong pane. Add an onChange
modifier to the body view that watches for changes to guardrail.activeBanner and
resets the confirmation dialog state (dismiss or set to nil/false) whenever the
active banner changes. This ensures that if a user has a confirmation dialog
open and the banner switches while the dialog is displayed, the dialog will be
dismissed rather than potentially confirming an action on the wrong banner.
Apply the same fix to the other location mentioned at lines 67-76.
🪄 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: 3be54940-619c-4303-9a94-cd58ddd6008e

📥 Commits

Reviewing files that changed from the base of the PR and between 51da1aa and 69b6384.

📒 Files selected for processing (6)
  • Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+ProcessInfo.swift
  • Resources/Localizable.xcstrings
  • Sources/PaneMemoryGuardrail.swift
  • Sources/PaneMemoryGuardrailBannerView.swift
  • Sources/Panels/BrowserPanelView.swift
  • cmuxTests/PaneMemoryGuardrailTests.swift

Comment thread Sources/PaneMemoryGuardrail.swift
@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

This branch was successfully deployed

1 active deployment
Preview – cmux — fb72ae87 Deployed Jun 19, 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.

Tab-switch crash in vertical sidebar on macOS 14.8.2 — objc use-after-free via AppKit, likely same root cause as #653

1 participant