Skip to content

Add recursive memory attribution diagnostics - #4437

Merged
austinywang merged 18 commits into
mainfrom
issue-4364-recursive-memory-attribution
May 20, 2026
Merged

austinywang merged 18 commits into
mainfrom
issue-4364-recursive-memory-attribution

Conversation

@austinywang

@austinywang austinywang commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a cmux memory diagnostic that separates direct cmux app footprint from recursive child-process RSS
  • group descendant RSS by command name and attribute top groups back to workspace/pane/surface when known
  • surface child-process RSS groups in the existing Task Manager and include the memory diagnostic in system.top JSON

Fixes #4364

Verification

  • xcrun swiftc -parse Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/TerminalControllerTopSupport.swift Sources/TerminalController.swift Sources/TaskManagerSnapshot.swift Sources/TaskManagerTypes.swift Sources/TaskManagerView.swift Sources/TaskManagerWindowController.swift CLI/cmux.swift
  • python3 -m json.tool Resources/Localizable.xcstrings
  • git diff --check

Did not run local tests, xcodebuild, or reload.sh per workspace instructions.


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Note

Medium Risk
Adds a new diagnostics surface (system.memory/cmux memory) and changes low-level process enumeration/caching, which could affect accuracy/perf of process/resource reporting if edge cases are missed.

Overview
Adds recursive memory diagnostics that separate the cmux app’s physical footprint from descendant process RSS, group child RSS by command, and attribute top groups back to workspace/pane/surface when possible.

Exposes this via a new system.memory v2 method and a cmux memory CLI command (with --all, --workspace, --groups, --json), and also embeds a memory_diagnostic block into existing system.top responses.

Updates Task Manager to display App Footprint/Child RSS summary metrics and a new Child Process RSS section, and refactors process sampling to use proc_listallpids/proc_pidinfo (RUSAGE v4) with a short-lived snapshot cache plus improved attribution extraction from the annotated window tree.

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


Summary by cubic

Adds recursive memory attribution that separates the cmux app footprint from recursive child-process RSS and attributes usage to workspace/pane/surface. Exposes diagnostics via the new cmux memory CLI, system.memory API, and updated Task Manager metrics/UI.

  • New Features

    • cmux memory with --all, --workspace, --groups, --json (defaults to top 12 groups), with localized help/errors.
    • Adds system.memory and embeds memory_diagnostic in system.top JSON.
    • Groups descendant RSS by command and attributes top groups to workspace/pane/surface (prefers surface-based attribution, collapses across panes to avoid double-counting).
    • Task Manager: new summary metrics (App Footprint, Child RSS) and a “Child Process RSS” section.
  • Refactors

    • Process sampling now uses proc_listallpids/proc_pidinfo + RUSAGE_INFO_V4, with a short-lived cached snapshot; extends scope-cache keying for proc_bsdinfo.
    • Preserves workspace/pane/surface attribution during sampling/caching; tightens socket parsing and attribution payload typing; normalizes string values.
    • Ensures Task Manager refresh runs on the main actor; whitelists system.memory in allowed methods; migrates Task Manager model to Swift Observation (@Observable, @Bindable).
    • Fixes proc_listallpids count handling to avoid truncated process lists; bridges missing process tree intermediates; extracts the cmux memory CLI into CMUXCLI+Memory.swift with dedicated parsing/rendering.

Written for commit 416808b. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • Added a memory diagnostics CLI command and integrated memory diagnostics into the Task Manager (App Footprint, Child RSS, “Child Process RSS” section, actions, optional JSON output).
  • Enhancements

    • Improved process sampling, attribution, grouping, caching and sorting for more accurate and faster memory summaries; memory added to top/system payloads.
  • Documentation

    • Added localized help/UI and error strings for memory diagnostics.
  • Bug Fixes

    • Clearer user-facing error when memory diagnostics are unsupported.

Review Change Stack

@vercel

vercel Bot commented May 20, 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 May 20, 2026 11:50am
cmux-staging Building Building Preview, Comment May 20, 2026 11:50am

@coderabbitai

coderabbitai Bot commented May 20, 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

Adds a new cmux memory diagnostic pipeline: proc_listallpids-based process enumeration with caching, grouped recursive child RSS with PID→workspace/pane/surface attribution, server RPC system.memory, Task Manager UI integration, a cmux memory CLI, and localization.

Changes

Memory Diagnostics with Child-Process Attribution

Layer / File(s) Summary
Process enumeration, snapshot, and cache
Sources/CmuxTopProcessEnumeration.swift, Sources/CmuxTopSnapshot.swift, Sources/CmuxTopProcessSnapshotCache.swift, Sources/CmuxTopSnapshotScopeCache.swift
Enumerates BSD processes via proc_listallpids/proc_pidinfo, extracts task/rusage and CPU metrics, builds CmuxTopProcessInfo records, exposes snapshot accessors (process lookup/descendants), adds scope cache key from proc_bsdinfo, and provides a locked cached capture API with clamped conversions.
Memory diagnostic grouping and payload
Sources/CmuxTopMemoryDiagnostics.swift
Collects descendant PIDs, aggregates RSS/resident bytes into groups by normalized display name, accumulates per-attribution totals, selects top groups, and composes a structured memory_diagnostic payload and localized summary string.
Top attribution helpers
Sources/TerminalControllerTopSupport.swift
Walks annotated windows/workspaces/tags/panes/surfaces/webviews to build PID→attribution records, assigns attributions with specificity/merge/ambiguity rules, and exposes helpers used by payload generation.
Server-side system.memory handler
Sources/TerminalController.swift
Adds system.memory RPC and includes memory_diagnostic in system.top; validates/clamps top_group_limit/group_limit, captures a cached snapshot, annotates windows with processes disabled, computes memory diagnostics, and returns sample + memory_diagnostic.
Task Manager types, snapshot, view, and controller
Sources/TaskManagerTypes.swift, Sources/TaskManagerSnapshot.swift, Sources/TaskManagerView.swift, Sources/TaskManagerWindowController.swift
Adds CmuxTaskManagerMemoryDiagnostic decoding and childMemoryAggregate row kind; snapshot stores optional memoryDiagnostic and derived childMemoryRows; view shows App Footprint and Child RSS and a Child Process RSS section; controller migrates to Observation, sorts child rows, and updates refresh/error handling.
CLI cmux memory subcommand
CLI/CMUXCLI+Memory.swift, CLI/cmux.swift
Registers memory command and help, implements MemoryCommandOptions parsing with strict flag validation, clamps --groups to 1–100 (default 12), builds payload (workspace normalization and caller), calls system.memory, and renders JSON or formatted text with attribution and localized errors.
Localization and project wiring
Resources/Localizable.xcstrings, cmux.xcodeproj/project.pbxproj
Adds localization keys for CLI help/errors and Task Manager memory labels; wires the new CmuxTop* Swift files and CLI file into the Xcode project build phases.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as "cmux (memory)"
  participant Server as "TerminalController.v2SystemMemory"
  participant Cache as "CmuxTopProcessSnapshotCache"
  participant Snapshot as "CmuxTopProcessSnapshot"
  participant Annotator as "v2TopMemoryAttributionByPID"
  User->>CLI: run `cmux memory` (flags)
  CLI->>Server: sendV2(system.memory, params)
  Server->>Cache: captureCached(includeProcessDetails:false, maximumAge)
  Cache->>Snapshot: return cached or build new snapshot
  Server->>Annotator: build PID→attribution from annotatedWindows
  Snapshot->>Server: memoryDiagnosticPayload(with attributions, topGroupLimit)
  Server-->>CLI: { sample, memory_diagnostic }
  CLI-->>User: render JSON or formatted text
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#4132: Touches the same Task Manager memory accounting and top snapshot pipeline that this PR extends.

"🐰 I hop through PIDs both near and far,
Counting child RSS beneath each star,
Grouping noisy helpers into top view,
Footprint and children, now shown true.
Hooray — a carrot for diagnostics!"


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift @Concurrent ❌ Error v2SystemTop and v2SystemMemory call CPU/IO-heavy CmuxTopProcessSnapshot.capture()/captureCached() without explicit actor hops in a @MainActor class marked nonisolated. Move snapshot capture calls to explicit DispatchQueue.global() hops or add @concurrent annotation and invoke via await with proper MainActor boundary management.
Cmux Swift File And Package Boundaries ❌ Error TerminalController.swift exceeds budget by 1,456 lines. PR adds 87 material lines (new system.memory handler) without required extraction exception or 200+ line reduction. Extract v2SystemMemory handler to TerminalControllerMemorySupport.swift file, following the existing TerminalControllerTopSupport.swift pattern for cleaner separation.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding recursive memory attribution diagnostics to distinguish cmux app footprint from child-process memory.
Linked Issues check ✅ Passed The PR implements all major coding objectives from #4364: separates app footprint from recursive child-process RSS, groups descendants by command, attributes to workspace/pane/surface, and exposes via new system.memory/cmux memory diagnostics.
Out of Scope Changes check ✅ Passed Changes are focused on memory diagnostics implementation. The migration to Swift Observation (Task Manager model/view) and visibility changes to utility functions are directly supporting the core feature; process snapshot caching and refactoring are implementation details for the diagnostics feature.
Cmux Swift Actor Isolation ✅ Passed MainActor model properly retains isolation in Observation migration; Task closures explicitly bound @MainActor; diagnostic models marked Sendable; cache protected with OSAllocatedUnfairLock.
Cmux Swift Blocking Runtime ✅ Passed Two OSAllocatedUnfairLock instances added for platform bridges with documented justification for non-async synchronous contexts. No blocking primitives in other new code.
Cmux No Hacky Sleeps ✅ Passed Custom check applies only to TypeScript/JavaScript/shell/build-scripts; PR contains only Swift code and configs. Swift timing is covered by separate check.
Cmux Swift Concurrency ✅ Passed Drops Combine, adopts Swift Observation (@Observable/@bindable), proper Task lifecycle management, OSAllocatedUnfairLock for caching. No problematic legacy patterns.
Cmux Swift Logging ✅ Passed No logging violations: runtime code avoids print/debugPrint/NSLog, CLI uses print() for output, error handling properly guarded, no MainActor Logger issues, no sensitive data exposed.
Cmux User-Facing Error Privacy ✅ Passed All user-facing errors use localized strings with safe, generic terminology; no vendor names, internal provider details, raw upstream messages, credentials, or unredacted payloads are exposed.
Cmux Full Internationalization ✅ Passed 30 new memory-related localization keys added with complete translations for all 19 supported locales; all user-facing Swift text uses String(localized:defaultValue:).
Cmux Swiftui State Layout ✅ Passed TaskManagerModel migrated to @Observable (modern), view uses @Bindable, list rows pass snapshots+closures only, state mutations in Task closures not during render.
Cmux Architecture Rethink ✅ Passed Uses required platform bridge (libproc snapshots) with documented ownership, Swift Observation recommended pattern, time-bounded cache without timing repairs, and idempotent lifecycle coordination.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or materially change standalone windows. TaskManagerWindowController's window is already properly registered as "cmux.taskManager" with close-shortcut ownership.
Description check ✅ Passed PR description covers changes, issue reference, and verification steps, but lacks testing details and demo video sections required by the template.
✨ 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-4364-recursive-memory-attribution

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 20, 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: 9

🤖 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 10262-10283: The new "memory" help text currently returned in the
case "memory" switch is a hard-coded English multiline string; replace this
literal with calls to the app's localization API (e.g., NSLocalizedString or the
project's localized wrapper) for each segment (Usage, Flags, Output, Example and
each flag description), use localized plural-aware APIs for "process/processes"
counts, and wire the resulting localized strings together in the same return
path (case "memory") and in the other affected loci (around lines referenced:
11709-11715, 11732-11742, 11882-11883, 12497-12560, 26418) so all user-facing
text is localized; then add matching entries to the string catalogs for every
supported locale (including plural forms) with keys unique to this command
(e.g., "memory_usage", "memory_flag_all", "memory_output_app_footprint",
"memory_example") so translators can provide equivalents for all locales.
- Around line 11893-11894: The catch block that matches CLIError by checking
error.message.hasPrefix("method_not_found:") should not expose the internal
method name; update the thrown CLIError message in that catch (the code using
error.message.hasPrefix(...) and throwing CLIError(...)) to a user-facing
message such as "cmux build is too old or lacks memory diagnostics support" or
similar that omits "system.memory" or any internal provider identifiers.

In `@Resources/Localizable.xcstrings`:
- Around line 113213-113225: The new localization keys
taskManager.summary.appFootprint and taskManager.summary.childRSS only include
en/ja; add entries for every locale already supported by this xcstrings catalog
following the repo’s accepted fallback pattern (create the same key under each
locale with translated values or the fallback marker/value used elsewhere),
ensuring all locale slots are present for both keys (also apply the same fix for
the other occurrences mentioned around the 113283–113317 range).

In `@Sources/CmuxTopSnapshot.swift`:
- Around line 328-370: The file exceeds size guidelines because new
diagnostics/enumeration helpers were added; split these into sibling Swift
source files by extracting the diagnostic aggregation, cache/state logic,
summary rendering, and process-enumeration helpers (e.g., functions/types
referenced around memoryDiagnosticPayload such as memoryDiagnosticGroups,
memoryDiagnosticSummaryText, CmuxTopProcessAttribution, descendantPIDs,
summaryPayload/summary and any new cache or aggregation types) into separate
files under Sources (for example DiagnosticsAggregation.swift,
MemorySummary.swift, ProcessEnumeration.swift, DiagnosticsCache.swift); move
related helper methods and small extensions there, preserve internal/public
access levels, add necessary imports and update any file-private references to
allow access from CmuxTopSnapshot, and run/build tests to ensure no symbol or
visibility breakages.
- Around line 844-857: The diagnostic summary built in memoryDiagnosticPayload()
uses hard-coded English text and units; replace the inline string concatenation
that builds summary (the initial var summary and the appended "; top child
group: ... from workspace ...") with a localized format looked up via
NSLocalizedString or String(localized:) (e.g. use a key like
"MemoryDiagnosticSummary" with placeholders) and build the string with
String.localizedStringWithFormat or String(format:locale:), passing formatted
values from Self.formatDiagnosticBytes(rssBytes)/childRSS/appFootprintBytes and
topGroup["name"]/workspace; also ensure formatDiagnosticBytes returns localized
units (or switch to ByteCountFormatter with current locale) and add the new
key(s) to the string catalogs for every supported locale.
- Around line 998-1013: The code misinterprets proc_listallpids(nil, 0) as a
byte count and divides by pidStride, under-allocating the PID buffer; in
allBSDProcesses() treat the initial return as a PID count (rename byteCount ->
pidCount for clarity), allocate pids with count = max(1, pidCount + 32) (not
pidCount / pidStride), and keep passing the buffer size in bytes to
proc_listallpids (buffer.count * pidStride); also ensure returnedBytes is
treated as a PID count when computing count = min(pids.count,
Int(returnedBytes)) so bsdInfo(for:) iterates the correct number of PIDs.

In `@Sources/TaskManagerSnapshot.swift`:
- Around line 129-132: The code unconditionally sets terminalSurfaceId to
surfaceId, causing non-terminal surfaces to show "View Terminal"; update the
initializer/creation site that assigns terminalSurfaceId (the code block that
sets workspaceId, surfaceId, terminalSurfaceId, processId) to only set
terminalSurfaceId when the surface is actually a terminal—e.g., check the
surface's type/attribution (use the existing surface object or helper like
isTerminalSurface/Surface.kind/.isTerminal) and set terminalSurfaceId =
surfaceId only if that check is true, otherwise set terminalSurfaceId = nil.
- Around line 63-71: The convenience initializer currently always passes an
empty array for childMemoryRows, which can create inconsistent snapshots when a
memoryDiagnostic is provided; change the call so childMemoryRows is derived from
the provided memoryDiagnostic (e.g. replace the literal [] with a computed value
like Self.childMemoryRows(from: memoryDiagnostic) or memoryDiagnostic.flatMap {
Self.childMemoryRows(from: $0) } ?? []) so that when memoryDiagnostic is non-nil
the initializer computes and supplies the appropriate childMemoryRows while
falling back to an empty array when absent; update or add the helper method
(e.g. Self.childMemoryRows(from:)) if needed and keep the other parameters
(rows, agentRows, aggregateRows via Self.programAggregateRows(from: rows),
total, sampledAt, memoryDiagnostic) unchanged.

In `@Sources/TerminalController.swift`:
- Around line 3844-3847: The code currently coerces non-integer or out-of-range
top_group_limit/group_limit values; instead change v2SurfaceSplitSized(params:)
so that if either "top_group_limit" or "group_limit" is present in params you
parse with v2Int and if parsing returns nil or the parsed value is not within
1...100 you return an invalid_params error; only when the key is absent fall
back to the default (12), and only after successful validation set
requestedLimit and compute topGroupLimit = min(max(1, requestedLimit), 100).
Ensure you apply the same presence+range check for both "top_group_limit" and
"group_limit" and reference v2Int, v2SurfaceSplitSized(params:), requestedLimit
and topGroupLimit when locating where to add the validation and error return.
🪄 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: d22e0890-244c-47eb-89b1-98dedf682fa3

📥 Commits

Reviewing files that changed from the base of the PR and between e258f03 and a15bcc8.

📒 Files selected for processing (10)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/CmuxTopSnapshot.swift
  • Sources/CmuxTopSnapshotScopeCache.swift
  • Sources/TaskManagerSnapshot.swift
  • Sources/TaskManagerTypes.swift
  • Sources/TaskManagerView.swift
  • Sources/TaskManagerWindowController.swift
  • Sources/TerminalController.swift
  • Sources/TerminalControllerTopSupport.swift

Comment thread CLI/cmux.swift Outdated
Comment thread CLI/cmux.swift Outdated
Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/CmuxTopSnapshot.swift Outdated
Comment thread Sources/CmuxTopSnapshot.swift Outdated
Comment thread Sources/CmuxTopSnapshot.swift Outdated
Comment thread Sources/TaskManagerSnapshot.swift
Comment thread Sources/TaskManagerSnapshot.swift
Comment thread Sources/TerminalController.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.

5 issues found across 10 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/CmuxTopSnapshot.swift Outdated
Comment thread Sources/TerminalControllerTopSupport.swift Outdated
Comment thread Sources/TaskManagerSnapshot.swift Outdated
Comment thread Sources/TaskManagerSnapshot.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/TaskManagerWindowController.swift (1)

130-140: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid surfacing raw error descriptions in Task Manager.

The catch path currently exposes String(describing: error) to users. Please return a sanitized localized message here and keep raw error details out of user-facing copy.

Suggested fix
         } catch {
             guard !Task.isCancelled else { return }
-            self?.errorMessage = String(describing: error)
+            self?.errorMessage = String(
+                localized: "taskManager.refresh.error",
+                defaultValue: "Unable to refresh Task Manager data."
+            )
         }

As per coding guidelines: user-facing errors/alerts/output must not expose raw upstream messages or internal details.

🤖 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/TaskManagerWindowController.swift` around lines 130 - 140, The catch
block in the Task created in TaskManagerWindowController (the Task that calls
TerminalController.shared.taskManagerTopPayload and builds a
CmuxTaskManagerSnapshot) currently assigns String(describing: error) to
self?.errorMessage and thus exposes raw error text; change this to set a
user-friendly, localized message (e.g., NSLocalizedString("Failed to refresh
tasks", comment: "")) on self?.errorMessage and move the raw error into a
non-user-facing log (use your existing logging facility or os_log) so internal
details are recorded but not shown to the user; ensure Task.isCancelled checks
remain and update any tests/usage that assert errorMessage content.
🤖 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/TaskManagerWindowController.swift`:
- Around line 130-140: The catch block in the Task created in
TaskManagerWindowController (the Task that calls
TerminalController.shared.taskManagerTopPayload and builds a
CmuxTaskManagerSnapshot) currently assigns String(describing: error) to
self?.errorMessage and thus exposes raw error text; change this to set a
user-friendly, localized message (e.g., NSLocalizedString("Failed to refresh
tasks", comment: "")) on self?.errorMessage and move the raw error into a
non-user-facing log (use your existing logging facility or os_log) so internal
details are recorded but not shown to the user; ensure Task.isCancelled checks
remain and update any tests/usage that assert errorMessage content.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85982ac4-b6ee-4498-ba32-13aa20a39910

📥 Commits

Reviewing files that changed from the base of the PR and between a15bcc8 and 0bde7df.

📒 Files selected for processing (5)
  • CLI/cmux.swift
  • Sources/CmuxTopSnapshot.swift
  • Sources/TaskManagerSnapshot.swift
  • Sources/TaskManagerWindowController.swift
  • Sources/TerminalController.swift

@greptile-apps

greptile-apps Bot commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a recursive memory attribution diagnostic that separates the cmux app footprint from descendant child-process RSS, groups children by command name, and attributes top groups back to workspace/pane/surface. The diagnostic is exposed via a new cmux memory CLI command, a new system.memory socket API, the existing system.top JSON response, and a new "Child Process RSS" section in the Task Manager.

  • Process enumeration is refactored to use proc_listallpids/proc_pidinfo + RUSAGE_INFO_V4, with a short-lived OSAllocatedUnfairLock-guarded cache; the previously-reported race between a weaker and richer concurrent snapshot write is now correctly resolved by re-checking freshness and detail level under the write lock.
  • Attribution walks the process tree to find the nearest known workspace/pane/surface, with specificity-ranked collision resolution and common-owner collapsing to avoid double-counting across panes.
  • Task Manager migrates to @Observable/@Bindable and adds App Footprint + Child RSS summary metrics alongside a new sortable "Child Process RSS" section.

Confidence Score: 5/5

Safe to merge; the new diagnostic path is additive and isolated from existing process sampling and Task Manager logic.

The two previously-flagged issues (cache write race, unlocalized 'Invalid workspace handle') are confirmed fixed. The remaining findings are minor style-level localization nits: one inline string interpolation in the CLI renderer and one internal error string that surfaces a socket method name. Neither affects correctness, data integrity, or user security.

No files require special attention; the changes are well-scoped to the new diagnostic surface.

Important Files Changed

Filename Overview
CLI/CMUXCLI+Memory.swift New cmux memory command with full option parsing, socket dispatch, and text renderer. One inline string interpolation (pid= label) skips localization; everything else uses String(localized:).
Sources/CmuxTopMemoryDiagnostics.swift New extension on CmuxTopProcessSnapshot providing memoryDiagnosticPayload, group accumulation, and summary text. Attribution walk-up, overflow-safe accumulation, and grouping logic look correct.
Sources/CmuxTopProcessEnumeration.swift Migrates process sampling to proc_listallpids/proc_pidinfo + RUSAGE_INFO_V4. Buffer-resize loop for allBSDProcesses correctly distinguishes pid count vs byte size. listedChildPIDs is structurally identical and treats return value as pid count consistently.
Sources/CmuxTopProcessSnapshotCache.swift Short-lived snapshot cache with re-check under the write lock (fixes the previously noted race). cachedSnapshotDetailsSatisfy correctly allows a richer includeProcessDetails:true snapshot to serve false requests.
Sources/CmuxTopSnapshot.swift Adds descendantPIDs that combines snapshot-derived children with live proc_listchildpids calls to bridge intermediates. Overflow-safe arithmetic and visited-set cycle guard look correct.
Sources/TerminalControllerTopSupport.swift New v2TopMemoryDiagnosticPayload and v2TopMemoryAttributionByPID with specificity-based collision resolution. Attribution logic (common-owner collapse, ambiguous-specificity tracking) is complex but structurally sound.
Sources/TerminalController.swift Adds system.memory handler. Handler calls v2SystemTopBasePayload for window context then re-annotates with a includeProcessDetails:true snapshot; one internal error string exposes the socket method name.
Sources/TaskManagerSnapshot.swift Adds childMemoryRows derived from CmuxTaskManagerMemoryDiagnostic; attribution detail helpers are correctly localized and guard against nil attributions.
Sources/TaskManagerWindowController.swift Migrates CmuxTaskManagerModel to @Observable/@Bindable; timer callback wraps refresh in @MainActor Task; isRefreshing state management looks correct.
Sources/TaskManagerView.swift Adds App Footprint / Child RSS summary metrics and a new "Child Process RSS" section; all new strings route through String(localized:).

Sequence Diagram

sequenceDiagram
    participant CLI as cmux memory CLI
    participant Socket as SocketClient
    participant TC as TerminalController (nonisolated)
    participant Main as MainActor
    participant Cache as SnapshotCache
    participant Diag as CmuxTopProcessSnapshot

    CLI->>Socket: sendV2("system.memory", params)
    Socket->>TC: v2SystemMemory(params)
    TC->>Main: "v2MainSync { v2SystemTopBasePayload }"
    Main-->>TC: windowNodes (workspace/pane/surface context)
    TC->>Cache: captureCached(includeProcessDetails: true, maxAge: 2s)
    Cache-->>TC: CmuxTopProcessSnapshot
    TC->>TC: v2AnnotateTopWindows(windowNodes, snapshot)
    TC->>Diag: memoryDiagnosticPayload(appPID, topGroupLimit, attributionByPID)
    Diag-->>TC: memory_diagnostic payload
    TC-->>Socket: .ok(payload)
    Socket-->>CLI: [String:Any]
    CLI->>CLI: renderMemoryText / JSON output
Loading

Reviews (8): Last reviewed commit: "Extract memory CLI command" | Re-trigger Greptile

Comment thread Resources/Localizable.xcstrings
Comment thread Sources/CmuxTopSnapshot.swift

@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 7 files (changes from recent commits).

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

Re-trigger cubic

Comment thread Sources/TerminalController.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.

Caution

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

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

358-385: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t let equal-specificity collisions pick an arbitrary surface.

Lines 364-370 now prevent less-specific overwrites, but equal-specificity conflicts still keep the first match, and Lines 374-385 only rank by workspace/pane/surface depth. Shared helper/browser PIDs can appear under multiple webviews at the same specificity, so top_attribution can end up pointing at the first traversed workspace/pane/surface instead of remaining ambiguous. Please drop or de-scope equal-specificity conflicts rather than keeping the first winner.

🤖 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/TerminalControllerTopSupport.swift` around lines 358 - 385, The
current assignTopMemoryAttribution keeps the first attribution when
specificities are equal; change it so equal-specificity collisions are not
retained: inside assignTopMemoryAttribution(_:,from:to:) compute let
existingSpec = v2TopMemoryAttributionSpecificity(existing) and let newSpec =
v2TopMemoryAttributionSpecificity(attribution), then if newSpec > existingSpec
replace result[pid] = attribution, if newSpec == existingSpec remove the mapping
(e.g. result.removeValue(forKey: pid) or set to nil) so equal-specificity PIDs
become ambiguous rather than pointing to the first traversed surface; keep the
current logic when existingSpec > newSpec to continue.
Sources/CmuxTopSnapshot.swift (1)

825-834: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't emit surface_type for workspace-only fallback attributions.

This branch runs when either cmuxWorkspaceID or cmuxSurfaceID is present, but it always sets surfaceType to "terminal". That produces payloads where surface_id is null while surface_type claims a concrete surface, which can mislead the new memory attribution UI/CLI.

Suggested fix
                 return CmuxTopProcessAttribution(
                     workspaceID: process.cmuxWorkspaceID,
                     workspaceRef: nil,
                     paneID: nil,
                     paneRef: nil,
                     surfaceID: process.cmuxSurfaceID,
                     surfaceRef: nil,
-                    surfaceType: "terminal",
+                    surfaceType: process.cmuxSurfaceID == nil ? nil : "terminal",
                     reason: process.cmuxAttributionReason ?? "cmux-process-scope"
                 )
🤖 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/CmuxTopSnapshot.swift` around lines 825 - 834, The current branch
that builds a CmuxTopProcessAttribution sets surfaceType to the literal
"terminal" even when cmuxSurfaceID is nil, producing misleading workspace-only
attributions; update the construction in Sources/CmuxTopSnapshot.swift so that
surfaceType is only populated when process.cmuxSurfaceID != nil (e.g., set
surfaceType to "terminal" when cmuxSurfaceID is present, otherwise pass nil),
keeping the other fields (workspaceID, surfaceID, reason) as-is and using the
existing CmuxTopProcessAttribution initializer and the
process.cmuxAttributionReason value.
♻️ Duplicate comments (1)
Sources/TerminalController.swift (1)

3863-3867: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ignore the legacy fallback key when top_group_limit is already set.

Line 3867 already gives top_group_limit precedence, but Lines 3863-3865 still reject the request if group_limit is malformed. That makes an unused fallback parameter a hard error and can break clients that send both keys during migration. Only validate group_limit when top_group_limit is absent.

Suggested fix
         let topGroupLimitValue = groupLimitParam("top_group_limit")
         if let invalidLimitKey {
             return .err(code: "invalid_params", message: "\(invalidLimitKey) must be an integer from 1 to 100", data: nil)
         }
-        let groupLimitValue = groupLimitParam("group_limit")
-        if let invalidLimitKey {
-            return .err(code: "invalid_params", message: "\(invalidLimitKey) must be an integer from 1 to 100", data: nil)
-        }
+        let groupLimitValue: Int?
+        if topGroupLimitValue == nil {
+            groupLimitValue = groupLimitParam("group_limit")
+            if let invalidLimitKey {
+                return .err(code: "invalid_params", message: "\(invalidLimitKey) must be an integer from 1 to 100", data: nil)
+            }
+        } else {
+            groupLimitValue = nil
+        }
         let topGroupLimit = topGroupLimitValue ?? groupLimitValue ?? 12
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/TerminalController.swift` around lines 3863 - 3867, The current logic
rejects malformed group_limit even when top_group_limit (topGroupLimitValue) is
present; change the flow so you only call/validate
groupLimitParam("group_limit") and check invalidLimitKey if topGroupLimitValue
is nil—i.e., if topGroupLimitValue != nil, skip invoking
groupLimitParam/invalidLimitKey entirely and let topGroupLimit be
topGroupLimitValue, otherwise fall back to parsing/validating group_limit and
then set topGroupLimit = groupLimitValue ?? 12; update references to
groupLimitParam, invalidLimitKey, topGroupLimitValue, and topGroupLimit
accordingly.
🤖 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/CmuxTopSnapshot.swift`:
- Around line 825-834: The current branch that builds a
CmuxTopProcessAttribution sets surfaceType to the literal "terminal" even when
cmuxSurfaceID is nil, producing misleading workspace-only attributions; update
the construction in Sources/CmuxTopSnapshot.swift so that surfaceType is only
populated when process.cmuxSurfaceID != nil (e.g., set surfaceType to "terminal"
when cmuxSurfaceID is present, otherwise pass nil), keeping the other fields
(workspaceID, surfaceID, reason) as-is and using the existing
CmuxTopProcessAttribution initializer and the process.cmuxAttributionReason
value.

In `@Sources/TerminalControllerTopSupport.swift`:
- Around line 358-385: The current assignTopMemoryAttribution keeps the first
attribution when specificities are equal; change it so equal-specificity
collisions are not retained: inside assignTopMemoryAttribution(_:,from:to:)
compute let existingSpec = v2TopMemoryAttributionSpecificity(existing) and let
newSpec = v2TopMemoryAttributionSpecificity(attribution), then if newSpec >
existingSpec replace result[pid] = attribution, if newSpec == existingSpec
remove the mapping (e.g. result.removeValue(forKey: pid) or set to nil) so
equal-specificity PIDs become ambiguous rather than pointing to the first
traversed surface; keep the current logic when existingSpec > newSpec to
continue.

---

Duplicate comments:
In `@Sources/TerminalController.swift`:
- Around line 3863-3867: The current logic rejects malformed group_limit even
when top_group_limit (topGroupLimitValue) is present; change the flow so you
only call/validate groupLimitParam("group_limit") and check invalidLimitKey if
topGroupLimitValue is nil—i.e., if topGroupLimitValue != nil, skip invoking
groupLimitParam/invalidLimitKey entirely and let topGroupLimit be
topGroupLimitValue, otherwise fall back to parsing/validating group_limit and
then set topGroupLimit = groupLimitValue ?? 12; update references to
groupLimitParam, invalidLimitKey, topGroupLimitValue, and topGroupLimit
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 98382a42-4fc0-41e5-81ae-4e158ec0b063

📥 Commits

Reviewing files that changed from the base of the PR and between 0bde7df and 3c5cc10.

📒 Files selected for processing (6)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/CmuxTopSnapshot.swift
  • Sources/TaskManagerSnapshot.swift
  • Sources/TerminalController.swift
  • Sources/TerminalControllerTopSupport.swift

coderabbitai[bot]
coderabbitai Bot previously requested changes May 20, 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 `@Sources/CmuxTopMemoryDiagnostics.swift`:
- Around line 167-170: The child groups are losing caller-supplied attribution
because you call nearestCMUXAttribution(for: pid) which rebuilds a fallback from
snapshot fields instead of honoring ancestor attributions; change the logic so
the caller-supplied attributionByPID[pid] is propagated into the ancestor-walk
used by nearestCMUXAttribution. Specifically, update the append call sites (the
expression groups[key]?.append(process: process, attribution:
attributionByPID[pid] ?? nearestCMUXAttribution(for: pid)) and the similar block
around lines 185-205) to supply the existing attribution as a preferred fallback
into the ancestor walk (e.g. add a parameter to nearestCMUXAttribution like
nearestCMUXAttribution(for: pid, preferred: attributionByPID[pid]) or make
nearestCMUXAttribution consult attributionByPID during its walk) so descendant
lookups return the caller-supplied pane/surface refs when available.

In `@Sources/CmuxTopProcessEnumeration.swift`:
- Around line 151-169: The allBSDProcesses() routine drops all results if the
pid buffer stays full (returnedCount >= pids.count), causing an empty snapshot
under high churn; change the loop to preserve and return the last successful pid
snapshot or grow/retry the buffer instead of falling through to return [] —
specifically, in allBSDProcesses() when proc_listallpids fills the buffer
(compare returnedCount and pids.count), either expand the pids buffer (e.g.,
double its size) and retry the call or keep the previous successful pids array
and convert that to bsdInfo entries via bsdInfo(for:) before returning, ensuring
you reference pidCount, pids, returnedCount and bsdInfo(for:) to locate and
modify the logic.
🪄 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: 9607e480-cbc8-4e2a-955e-454ec59cb5b7

📥 Commits

Reviewing files that changed from the base of the PR and between 3c5cc10 and 62d8733.

📒 Files selected for processing (7)
  • Resources/Localizable.xcstrings
  • Sources/CmuxTopMemoryDiagnostics.swift
  • Sources/CmuxTopProcessEnumeration.swift
  • Sources/CmuxTopProcessSnapshotCache.swift
  • Sources/CmuxTopSnapshot.swift
  • Sources/TaskManagerWindowController.swift
  • cmux.xcodeproj/project.pbxproj

Comment thread Sources/CmuxTopMemoryDiagnostics.swift
Comment thread Sources/CmuxTopProcessEnumeration.swift Outdated
@austinywang
austinywang dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] May 20, 2026 10:44

Dismissed stale CodeRabbit changes-requested review after all threads were addressed and the latest CodeRabbit pass succeeded on the current head.

Comment thread CLI/cmux.swift
Comment thread Sources/CmuxTopMemoryDiagnostics.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 20, 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

♻️ Duplicate comments (1)
Sources/TerminalController.swift (1)

3880-3888: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate group_limit even when top_group_limit is present.

Line 3881 skips group_limit parsing whenever top_group_limit parsed successfully, so a request like { "top_group_limit": 12, "group_limit": "abc" } is accepted instead of returning invalid_params. Please validate the legacy alias whenever it is present, then let top_group_limit win only if both values are valid.

Suggested fix
-        let groupLimitValue: Int?
-        if topGroupLimitValue == nil {
-            groupLimitValue = groupLimitParam("group_limit")
-            if let invalidLimitKey {
-                return .err(code: "invalid_params", message: "\(invalidLimitKey) must be an integer from 1 to 100", data: nil)
-            }
-        } else {
-            groupLimitValue = nil
-        }
+        let groupLimitValue = groupLimitParam("group_limit")
+        if let invalidLimitKey {
+            return .err(code: "invalid_params", message: "\(invalidLimitKey) must be an integer from 1 to 100", data: nil)
+        }
         let topGroupLimit = topGroupLimitValue ?? groupLimitValue ?? 12

Based on learnings, v2SurfaceSplitSized(params:) should treat a present-but-invalid numeric parameter as invalid_params.

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

In `@Sources/TerminalController.swift` around lines 3880 - 3888, In
v2SurfaceSplitSized(params:) change the logic so group_limit is always parsed
via groupLimitParam("group_limit") (and checked for invalidLimitKey) even when
topGroupLimitValue is non-nil; if groupLimitParam reports invalidLimitKey return
the same .err(code: "invalid_params", ...) immediately, otherwise compute the
effective value by preferring topGroupLimitValue when both parsed successfully
(i.e., let topGroupLimitValue win only after validating groupLimitValue). Ensure
you reference and keep using groupLimitValue, topGroupLimitValue,
groupLimitParam("group_limit"), and invalidLimitKey in the updated flow.
🤖 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/TerminalControllerTopSupport.swift`:
- Around line 393-399: When newSpecificity == existingSpecificity don't
immediately remove result[pid]; instead attempt to collapse the two attributions
into their common owner and preserve that attribution. Concretely: in the
equality branch (where v2TopMemoryAttributionSpecificity(existing) ==
newSpecificity), compute a shared/common ancestor attribution from existing and
attribution (e.g. with a utility like
v2TopMemoryAttributionCommonAncestor(existing, attribution) or implement a small
helper to walk their owner/pane/workspace ancestry), and if a common owner
exists set result[pid] = commonOwner; only if no common owner can be found then
removeValue(forKey: pid) and set ambiguousSpecificityByPID[pid] =
newSpecificity. This preserves shared-process attribution to the common owner
and marks ambiguous only when no common owner remains.

---

Duplicate comments:
In `@Sources/TerminalController.swift`:
- Around line 3880-3888: In v2SurfaceSplitSized(params:) change the logic so
group_limit is always parsed via groupLimitParam("group_limit") (and checked for
invalidLimitKey) even when topGroupLimitValue is non-nil; if groupLimitParam
reports invalidLimitKey return the same .err(code: "invalid_params", ...)
immediately, otherwise compute the effective value by preferring
topGroupLimitValue when both parsed successfully (i.e., let topGroupLimitValue
win only after validating groupLimitValue). Ensure you reference and keep using
groupLimitValue, topGroupLimitValue, groupLimitParam("group_limit"), and
invalidLimitKey in the updated flow.
🪄 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: 36424604-8098-4877-b2de-ce178f579921

📥 Commits

Reviewing files that changed from the base of the PR and between 62d8733 and 259087e.

📒 Files selected for processing (7)
  • CLI/cmux.swift
  • Resources/Localizable.xcstrings
  • Sources/CmuxTopMemoryDiagnostics.swift
  • Sources/CmuxTopProcessEnumeration.swift
  • Sources/CmuxTopProcessSnapshotCache.swift
  • Sources/TerminalController.swift
  • Sources/TerminalControllerTopSupport.swift
💤 Files with no reviewable changes (1)
  • Resources/Localizable.xcstrings

Comment thread Sources/TerminalControllerTopSupport.swift
coderabbitai[bot]
coderabbitai Bot previously requested changes May 20, 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/TerminalControllerTopSupport.swift`:
- Around line 399-412: The comparison logic currently uses
v2TopMemoryAttributionSpecificity(existing) (existingSpecificity) which can
differ from the recorded collapse specificity in
commonOwnerSourceSpecificityByPID; change the branches to compare newSpecificity
against the collapse/source-specificity used for the stored owner (use
mergedSourceSpecificity or commonOwnerSourceSpecificityByPID[pid] when present)
instead of existingSpecificity so a later weaker attribution cannot incorrectly
overwrite or ambiguate a PID; update the if/else conditions around
v2TopMemoryAttributionCommonOwner(existing, attribution), the newSpecificity >
... and newSpecificity == ... checks to use mergedSourceSpecificity (or its
resolved value) and ensure commonOwnerSourceSpecificityByPID and
ambiguousSpecificityByPID are updated consistently with mergedSourceSpecificity.
🪄 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: 12d303da-2571-4d5d-91b6-6b2512f662c1

📥 Commits

Reviewing files that changed from the base of the PR and between 961cdfa and e26e437.

📒 Files selected for processing (7)
  • Resources/Localizable.xcstrings
  • Sources/CmuxTopProcessSnapshotCache.swift
  • Sources/TaskManagerSnapshot.swift
  • Sources/TaskManagerTypes.swift
  • Sources/TaskManagerView.swift
  • Sources/TaskManagerWindowController.swift
  • Sources/TerminalControllerTopSupport.swift

Comment thread Sources/TerminalControllerTopSupport.swift
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 20, 2026 11:20

Dismissed stale CodeRabbit changes-requested review from e26e437 after its finding was fixed in 742e24f, its inline thread was resolved, and CodeRabbit posted a newer COMMENTED review on the current head.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 20, 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/CmuxTopProcessEnumeration.swift`:
- Around line 160-175: The code incorrectly divides proc_listallpids' return
value by pidStride; proc_listallpids already returns a PID count, so remove
those divisions: compute initialPIDCount directly from initialByteCount (e.g.,
initialPIDCount = max(1, initialByteCount)) and compute returnedPIDCount as
Int(returnedByteCount) (no / pidStride). Update uses of returnedPIDCount and any
logic that derived capacity from initialPIDCount accordingly
(functions/variables to change: proc_listallpids calls, initialByteCount,
initialPIDCount, returnedByteCount, returnedPIDCount, capacity, and the loop
that builds pids and calls bsdInfos/from lastPIDs).
🪄 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: c70babc5-fb07-4305-a2c8-9976a658fc7d

📥 Commits

Reviewing files that changed from the base of the PR and between e26e437 and 742e24f.

📒 Files selected for processing (5)
  • Resources/Localizable.xcstrings
  • Sources/CmuxTopProcessEnumeration.swift
  • Sources/TaskManagerSnapshot.swift
  • Sources/TaskManagerTypes.swift
  • Sources/TerminalControllerTopSupport.swift

Comment thread Sources/CmuxTopProcessEnumeration.swift Outdated
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 20, 2026 11:48

Stale CodeRabbit change request: the proc_listallpids return-value finding was fixed in f62bd35, the inline thread is resolved/outdated, and there are no unresolved review threads.

@austinywang
austinywang merged commit bea8551 into main May 20, 2026
17 of 18 checks passed
lawrencecchen added a commit that referenced this pull request May 22, 2026
…64.8 leak class

Red commit in the two-commit regression pattern documented in
repo/CLAUDE.md.

PR #4437 migrated
CmuxTaskManagerModel to @observable and CmuxTaskManagerView started
holding @bindable var model while rendering
ScrollView { LazyVStack { ForEach { ... } } }. That violates the
"Snapshot boundary for list subtrees" rule documented in repo/CLAUDE.md
(citing #2586): no view below
a lazy-list boundary may hold a reference to an Observable store. The
3 s refresh timer in TaskManagerWindowController constantly mutates
model.snapshot, which orthogonally invalidated every row and thrashed
LazyLayoutViewCache, allocating AttributeGraph nodes that never freed.
Matches the hang trace posted by @ma-pony on issue
#4529.

Refactor to the IndexSectionActions / SectionGapActions pattern in
Sources/SessionIndexView.swift:

- CmuxTaskManagerListView is a new value-typed inner view that owns the
  ScrollView/LazyVStack subtree. It receives value-typed row arrays and
  a CmuxTaskManagerRowActions closure bundle; it never sees the model.
- CmuxTaskManagerRowActions bundles viewWorkspace/viewTerminal/
  killProcess/activate closures. Bound to the model once per render at
  the outer view, mirroring FeedRowActions.bound() in Feed/FeedPanelView.swift.
- CmuxTaskManagerRow and CmuxTaskManagerResources gain Equatable so the
  row view's == has something to compare.
- CmuxTaskManagerSectionHeaderView gains Equatable comparing only the
  header payload.
- CmuxTaskManagerRowView gains Equatable, but `==` intentionally
  returns `false` in this commit so the new
  TaskManagerViewSnapshotBoundaryTests fail at runtime (XCTAssertEqual
  on two row views with the same payload returns false). With
  .equatable() wired at every ForEach call site, a false-returning ==
  defeats the optimization and re-introduces the per-tick row body
  re-evaluation that drives the leak. The follow-up commit fixes == to
  compare only the value-typed `row` payload.
- The outer CmuxTaskManagerView keeps @bindable var model for the
  toolbar/summary/sort header. Those live above the snapshot boundary
  and need to repaint on snapshot mutation.

Refs #4529

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lawrencecchen added a commit that referenced this pull request May 22, 2026
#4555)

* test: refactor TaskManagerView with broken row Equatable to expose 0.64.8 leak class

Red commit in the two-commit regression pattern documented in
repo/CLAUDE.md.

PR #4437 migrated
CmuxTaskManagerModel to @observable and CmuxTaskManagerView started
holding @bindable var model while rendering
ScrollView { LazyVStack { ForEach { ... } } }. That violates the
"Snapshot boundary for list subtrees" rule documented in repo/CLAUDE.md
(citing #2586): no view below
a lazy-list boundary may hold a reference to an Observable store. The
3 s refresh timer in TaskManagerWindowController constantly mutates
model.snapshot, which orthogonally invalidated every row and thrashed
LazyLayoutViewCache, allocating AttributeGraph nodes that never freed.
Matches the hang trace posted by @ma-pony on issue
#4529.

Refactor to the IndexSectionActions / SectionGapActions pattern in
Sources/SessionIndexView.swift:

- CmuxTaskManagerListView is a new value-typed inner view that owns the
  ScrollView/LazyVStack subtree. It receives value-typed row arrays and
  a CmuxTaskManagerRowActions closure bundle; it never sees the model.
- CmuxTaskManagerRowActions bundles viewWorkspace/viewTerminal/
  killProcess/activate closures. Bound to the model once per render at
  the outer view, mirroring FeedRowActions.bound() in Feed/FeedPanelView.swift.
- CmuxTaskManagerRow and CmuxTaskManagerResources gain Equatable so the
  row view's == has something to compare.
- CmuxTaskManagerSectionHeaderView gains Equatable comparing only the
  header payload.
- CmuxTaskManagerRowView gains Equatable, but `==` intentionally
  returns `false` in this commit so the new
  TaskManagerViewSnapshotBoundaryTests fail at runtime (XCTAssertEqual
  on two row views with the same payload returns false). With
  .equatable() wired at every ForEach call site, a false-returning ==
  defeats the optimization and re-introduces the per-tick row body
  re-evaluation that drives the leak. The follow-up commit fixes == to
  compare only the value-typed `row` payload.
- The outer CmuxTaskManagerView keeps @bindable var model for the
  toolbar/summary/sort header. Those live above the snapshot boundary
  and need to repaint on snapshot mutation.

Refs #4529

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: compare CmuxTaskManagerRowView only by row payload so .equatable() can skip body re-eval

Green commit: the `==` operator now compares only the value-typed `row`
payload, ignoring the closure identities the parent rebuilds every
render tick. With `.equatable()` wired at each ForEach call site (added
in the red commit's refactor), SwiftUI can now prove that two row views
with the same payload are interchangeable, suppressing body
re-evaluation on the 3 s `model.snapshot` mutation.

This closes the snapshot-boundary class of leak described in
repo/CLAUDE.md (citing #2586)
and the secondary leak vector from
#4529. The dominant vector
(git probe ancestor walk on macOS 14/15) is fixed separately in
#4552.

testTaskManagerRowViewEqualityIgnoresClosureIdentity flips from RED to
GREEN with this commit; testTaskManagerRowViewEqualityDetectsRowChanges
continues to assert the inverse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address PR review: stabilize PID ordering, tighten actor isolation, restore section header access

Three findings on #4555:

- CodeRabbit (Major): the snapshot producers already sort PID arrays
  before sending, but synthesized Equatable on CmuxTaskManagerRow /
  CmuxTaskManagerResources compares them by array order. Normalize
  rootProcessIds, foregroundProcessGroupIds, and processIds at init so
  the invariant ("these arrays are stored deduped + ascending") lives
  in one place and a future producer that forgets to sort cannot
  silently reopen the cache-thrash path this PR is closing.
- Greptile (P2): annotate the closure types on CmuxTaskManagerRowActions
  and CmuxTaskManagerRowView with @mainactor. The actions are bound
  through @mainactor CmuxTaskManagerModel methods and the row view
  invokes them from SwiftUI button taps. Making the isolation explicit
  lets Swift 6 catch any future off-MainActor forwarding at compile time.
- Greptile (P2): revert CmuxTaskManagerSectionHeaderView from internal
  back to private. Only CmuxTaskManagerRowView needs broader access for
  the regression test; the section header has no caller outside this
  file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ShubhamPatilsd pushed a commit to emergent-inc/mosaic that referenced this pull request Jul 9, 2026
…k (#4555)

* test: refactor TaskManagerView with broken row Equatable to expose 0.64.8 leak class

Red commit in the two-commit regression pattern documented in
repo/CLAUDE.md.

PR manaflow-ai/cmux#4437 migrated
CmuxTaskManagerModel to @observable and CmuxTaskManagerView started
holding @bindable var model while rendering
ScrollView { LazyVStack { ForEach { ... } } }. That violates the
"Snapshot boundary for list subtrees" rule documented in repo/CLAUDE.md
(citing manaflow-ai/cmux#2586): no view below
a lazy-list boundary may hold a reference to an Observable store. The
3 s refresh timer in TaskManagerWindowController constantly mutates
model.snapshot, which orthogonally invalidated every row and thrashed
LazyLayoutViewCache, allocating AttributeGraph nodes that never freed.
Matches the hang trace posted by @ma-pony on issue
manaflow-ai/cmux#4529.

Refactor to the IndexSectionActions / SectionGapActions pattern in
Sources/SessionIndexView.swift:

- CmuxTaskManagerListView is a new value-typed inner view that owns the
  ScrollView/LazyVStack subtree. It receives value-typed row arrays and
  a CmuxTaskManagerRowActions closure bundle; it never sees the model.
- CmuxTaskManagerRowActions bundles viewWorkspace/viewTerminal/
  killProcess/activate closures. Bound to the model once per render at
  the outer view, mirroring FeedRowActions.bound() in Feed/FeedPanelView.swift.
- CmuxTaskManagerRow and CmuxTaskManagerResources gain Equatable so the
  row view's == has something to compare.
- CmuxTaskManagerSectionHeaderView gains Equatable comparing only the
  header payload.
- CmuxTaskManagerRowView gains Equatable, but `==` intentionally
  returns `false` in this commit so the new
  TaskManagerViewSnapshotBoundaryTests fail at runtime (XCTAssertEqual
  on two row views with the same payload returns false). With
  .equatable() wired at every ForEach call site, a false-returning ==
  defeats the optimization and re-introduces the per-tick row body
  re-evaluation that drives the leak. The follow-up commit fixes == to
  compare only the value-typed `row` payload.
- The outer CmuxTaskManagerView keeps @bindable var model for the
  toolbar/summary/sort header. Those live above the snapshot boundary
  and need to repaint on snapshot mutation.

Refs manaflow-ai/cmux#4529

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: compare CmuxTaskManagerRowView only by row payload so .equatable() can skip body re-eval

Green commit: the `==` operator now compares only the value-typed `row`
payload, ignoring the closure identities the parent rebuilds every
render tick. With `.equatable()` wired at each ForEach call site (added
in the red commit's refactor), SwiftUI can now prove that two row views
with the same payload are interchangeable, suppressing body
re-evaluation on the 3 s `model.snapshot` mutation.

This closes the snapshot-boundary class of leak described in
repo/CLAUDE.md (citing manaflow-ai/cmux#2586)
and the secondary leak vector from
manaflow-ai/cmux#4529. The dominant vector
(git probe ancestor walk on macOS 14/15) is fixed separately in
manaflow-ai/cmux#4552.

testTaskManagerRowViewEqualityIgnoresClosureIdentity flips from RED to
GREEN with this commit; testTaskManagerRowViewEqualityDetectsRowChanges
continues to assert the inverse.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Address PR review: stabilize PID ordering, tighten actor isolation, restore section header access

Three findings on manaflow-ai/cmux#4555:

- CodeRabbit (Major): the snapshot producers already sort PID arrays
  before sending, but synthesized Equatable on CmuxTaskManagerRow /
  CmuxTaskManagerResources compares them by array order. Normalize
  rootProcessIds, foregroundProcessGroupIds, and processIds at init so
  the invariant ("these arrays are stored deduped + ascending") lives
  in one place and a future producer that forgets to sort cannot
  silently reopen the cache-thrash path this PR is closing.
- Greptile (P2): annotate the closure types on CmuxTaskManagerRowActions
  and CmuxTaskManagerRowView with @mainactor. The actions are bound
  through @mainactor CmuxTaskManagerModel methods and the row view
  invokes them from SwiftUI button taps. Making the isolation explicit
  lets Swift 6 catch any future off-MainActor forwarding at compile time.
- Greptile (P2): revert CmuxTaskManagerSectionHeaderView from internal
  back to private. Only CmuxTaskManagerRowView needs broader access for
  the regression test; the section header has no caller outside this
  file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 416808bc Deployed May 20, 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.

Nightly memory spikes need recursive child-process attribution for xcodebuild/swift-frontend

1 participant