Skip to content

Fix Task Manager CPU samples - #3588

Merged
austinywang merged 16 commits into
mainfrom
issue-3583-task-manager-cpu-zero
May 7, 2026
Merged

austinywang merged 16 commits into
mainfrom
issue-3583-task-manager-cpu-zero

Conversation

@austinywang

@austinywang austinywang commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #3583.

This keeps Task Manager and cmux top on the same shared system.top payload, but changes the leaf CPU sampler from the stale kinfo_proc.p_pctcpu field to a real rate calculation from PROC_PIDTASKINFO cumulative CPU time.

Root Cause

CmuxTopProcessSnapshot.processInfo in Sources/CmuxTopSnapshot.swift returned:

cpuPercent: max(0, Double(kinfo.kp_proc.p_pctcpu) / cpuScale * 100.0)

That field is zero for the process rows exposed by the Task Manager / cmux top path on current macOS, so CmuxTopProcessSnapshot.summary(for:) correctly summed zeros for every leaf, and every aggregate row also displayed 0.0%.

The regression was introduced with the original Task Manager/top implementation in 971372531fcc7141c5da87c5bdd724da1f5ee0b6 (Add top snapshots and Task Manager window (#3290)), which made kinfo_proc.p_pctcpu the CPU source while memory already came from proc_pidinfo(PROC_PIDTASKINFO).

Fix

  • Add a behavioral regression test that spawns a busy child process and asserts CmuxTopProcessSnapshot.summary(for:) reports non-zero CPU after a second capture.
  • Add CmuxTopProcessCPUTracker as the single owner for CPU sample history.
  • Key previous samples by the existing process identity key (pid + process start time), not PID alone.
  • Compute CPU as:
(delta(pti_total_user + pti_total_system) converted via mach_timebase_info / delta(monotonic wall-clock ns)) * 100

Task Manager rows and CLI formatting continue to consume the same cpu_percent payload field; no UI redesign or new metric was added.

Validation

  • ./scripts/reload.sh --tag issue-3583-task-manager-cpu-zero --launch built and launched the tagged app during validation.
  • With a temporary cmux workspace running yes > /dev/null, tagged cmux top --processes showed non-zero CPU for the active process before the final Rosetta wall-clock correction.
  • A direct PROC_PIDTASKINFO probe confirmed the busy process reports live cumulative CPU deltas; the final implementation uses monotonic wall-clock nanoseconds to avoid Rosetta mach_absolute_time() unit mismatch.

Local unit tests were not run per repo policy; CI should run the new regression test.

Testing

  • swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift
  • python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv
  • plutil -lint GhosttyTabs.xcodeproj/project.pbxproj
  • git diff --check
  • GitHub, Vercel, Greptile, CodeRabbit, Cursor, and CircleCI checks are monitored on PR Fix Task Manager CPU samples #3588.
  • Not run locally: xcodebuild or local app test bundles, per repo policy and this PR's build-safety instructions.

Checklist

  • Fixes Task Manager: CPU column always shows 0.0% (also affects cmux top) #3583.
  • Keeps Task Manager and cmux top on the shared system.top / cpu_percent payload path.
  • Adds regression coverage for busy-process non-zero CPU and overflow-sentinel CPU samples.
  • Addresses Greptile and CodeRabbit review feedback.
  • Avoids direct xcodebuild; final validation uses the required tagged reload command.

Note

Medium Risk
Updates low-level process sampling and caching/locking around system.top, which could affect Task Manager/cmux top correctness or performance across macOS versions.

Overview
Fixes Task Manager/cmux top CPU% reporting by replacing the stale kinfo_proc.p_pctcpu field with a rate computed from PROC_PIDTASKINFO cumulative CPU time deltas over a monotonic clock, while keeping the existing system.top payload shape (cpu_percent).

Adds CmuxTopProcessCPUTracker to own per-process CPU sample history (keyed by pid+start time), handle overlapping/out-of-order captures, and prune inactive keys safely. Also refactors CMUX scope caching to use OSAllocatedUnfairLock and moves the WebKit content-process PID helper into CmuxTopProcessDetails.swift.

Includes a new regression test suite (CmuxTopProcessCPUTests) that validates non-zero CPU for a busy child process and zero CPU for overflow-sentinel samples.

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


Summary by cubic

Fix CPU% in Task Manager and cmux top by computing rates from PROC_PIDTASKINFO deltas on a monotonic clock with a locked per‑process history, and harden CMUX scope caching against PID reuse. Keeps the existing system.top payload and returns 0% if the timebase ratio can’t be loaded.

  • Bug Fixes

    • Compute CPU% from pti_total_user + pti_total_system over CLOCK_UPTIME_RAW ns via mach_timebase_info; fall back to 0% if the ratio is unavailable.
    • Add CmuxTopProcessCPUTracker to compute+commit in one locked step, key by process identity (pid + start time), ignore stale overlapping samples, and prune only from the newest capture.
    • Prevent stale CMUX scope cache entries by revalidating pid+start‑time before and after KERN_PROCARGS2; only cache when the identity still matches.
  • Refactors

    • Collapse the two‑pass CPU application into a single tuple flow to avoid parallel‑array joins.
    • Mark snapshot types/helpers and tracker state nonisolated; replace NSLock+global scope cache with OSAllocatedUnfairLock, move CMUX scope parsing next to the cache, keep process‑detail helpers private, and extract the WebKit PID helper to CmuxTopProcessDetails.swift.

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

Summary by CodeRabbit

  • New Features

    • More accurate per-process CPU% reporting via timestamped sampling with overflow and out-of-order-sample protection.
    • Added ability to identify web content process PIDs for improved process details.
  • Refactor

    • Concurrency and cache redesign for more reliable snapshotting and scope lookups.
    • Process snapshot now defers CPU population until sampling completes; resource aggregation clamping improved.
  • Tests

    • Added tests validating CPU tracking, overflow handling, and detection of busy processes.

Add a behavioral unit test that launches a CPU-bound child process, captures the top snapshot twice, and expects the second sample to report non-zero CPU for that process. The current implementation reads the zero-valued kinfo p_pctcpu field, so this locks the regression at the sampler boundary shared by Task Manager and cmux top.

Constraint: Local tests are intentionally not run in this repo; CI owns test execution.

Rejected: UI-only Task Manager assertion | the same zero reaches cmux top, so the sampler boundary is the correct behavioral seam.

Confidence: high

Scope-risk: narrow

Tested: Not run locally per repository policy.

Not-tested: CI has not run this failing-test-only commit yet.
The top sampler was reading kinfo_proc.p_pctcpu, which is zero for the processes exposed through the Task Manager/cmux top pipeline on current macOS. Keep the existing rollup and presentation code, but make CmuxTopProcessSnapshot own CPU sampling by recording PROC_PIDTASKINFO total_user+total_system counters per process identity and calculating a rate against monotonic wall-clock nanoseconds on the next capture.

Constraint: Task Manager and cmux top share the same system.top payload; fix the leaf sampler instead of patching UI rows or CLI formatting.

Rejected: Poll p_pctcpu again after a delay | the API source is stale/zero for this use path and would keep the first sample wrong.

Rejected: Key previous samples by PID only | PID reuse would mix unrelated processes, so the existing pid+start-time key is reused.

Confidence: high

Scope-risk: narrow

Directive: Keep cpu_percent sourced from CmuxTopProcessSnapshot so Task Manager and cmux top cannot diverge.

Tested: ./scripts/reload.sh --tag issue-3583-task-manager-cpu-zero --launch built and launched before the Rosetta wall-clock correction; tagged cmux top then showed non-zero CPU for a busy yes process, revealing the final unit adjustment.

Not-tested: Local unit tests were not run per repository policy; CI must run the new regression test.
Merged origin/main before opening the PR so CI runs against the current workflow set. The merge only brings in the latest CI auto-approve workflow/script additions and does not change the CPU sampling implementation.

Constraint: iterate-pr workflow requires syncing the PR branch with the base branch before CI iteration.

Confidence: high

Scope-risk: narrow

Directive: Treat this as a base-branch sync only; CPU behavior lives in the two preceding commits.

Tested: Git merge completed without conflicts.

Not-tested: Local tests not run per repository policy.
@vercel

vercel Bot commented May 5, 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 7, 2026 1:33am
cmux-staging Building Building Preview, Comment May 7, 2026 1:33am

@coderabbitai

coderabbitai Bot commented May 5, 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 sampling-based per-process CPU tracking and conversion helpers, integrates sampling into top snapshot construction and payloads, replaces scope-cache locking with an unfair lock, adds process-introspection helpers (WKWebView PID), includes unit tests for CPU sampling, and updates the Xcode project to include new sources and tests.

Changes

CPU tracking + process details integration

Layer / File(s) Summary
Data Shape
Sources/CmuxTopProcessCPUTracker.swift, Sources/CmuxTopSnapshot.swift
Adds CmuxTopProcessCPUSample (totalTimeTicks, sampledAtNanoseconds). Changes CmuxTopProcessInfo.cpuPercent from let to var and defaults it to 0 during construction.
Sampling primitives & conversion
Sources/CmuxTopProcessCPUTracker.swift
Implements CmuxTopProcessCPUTracker (OSAllocatedUnfairLock-protected store, per-key samples, prune watermark). Adds Mach timebase ratio and tick→ns conversion plus overflow clamping helpers.
Snapshot extension helpers
Sources/CmuxTopProcessCPUTracker.swift
Adds cpuSampleClockNanoseconds(), cpuSample(from:sampledAtNanoseconds:), and cpuPercent(current:previous:) on CmuxTopProcessSnapshot and delegates percentage computation to the global tracker.
Process introspection utilities
Sources/CmuxTopProcessDetails.swift
Adds CmuxWebContentProcessIdentifier.pid(for:) which uses Objective-C runtime to invoke WKWebView’s private _webProcessIdentifier selector and return a positive PID or nil.
Snapshot workflow integration
Sources/CmuxTopSnapshot.swift
Refactors allProcesses() / processInfo(...) to capture a sampling timestamp, build a per-scope CPU-sample cache from proc_pidtaskinfo, compute cpuPercentages via the tracker, assign computed percentages into CmuxTopProcessInfo, update cpu_source to PROC_PIDTASKINFO totals, and use clampedAdd for saturating aggregates.
Scope cache sync
Sources/CmuxTopSnapshotScopeCache.swift
Replaces previous NSLock + global dictionary with an OSAllocatedUnfairLock-guarded cache and moves missing-scope computation outside the lock.
Tests
cmuxTests/CmuxTopProcessCPUTests.swift
Adds tests: testOverflowSentinelReportsZeroCPUPercent and testBusyChildProcessReportsNonZeroCPUPercent, plus helpers to poll CPU percent and terminate spawned processes.
Project wiring
GhosttyTabs.xcodeproj/project.pbxproj
Adds PBXFileReference / PBXBuildFile entries and updates Sources/cmuxTests groups and targets to include the new source and test files.

Sequence Diagram

sequenceDiagram
    actor Main as Main Thread
    participant Snapshot as CmuxTopSnapshot
    participant Clock as System Clock
    participant ProcInfo as proc_pidtaskinfo
    participant Tracker as CmuxTopProcessCPUTracker

    Main->>Snapshot: allProcesses(includeProcessDetails)
    Snapshot->>Clock: cpuSampleClockNanoseconds()
    Clock-->>Snapshot: now (ns)

    loop per-process
        Snapshot->>ProcInfo: proc_pidtaskinfo(pid)
        ProcInfo-->>Snapshot: task info (user/system ticks)
        Snapshot->>Tracker: record/update sample for scope key
    end

    Snapshot->>Tracker: cpuPercentages(activeKeys, sampledAt)
    Tracker->>Tracker: compute tick delta → ns, calc per-key %
    Tracker-->>Snapshot: per-scope cpu% map

    Snapshot->>Snapshot: apply cpu% to CmuxTopProcessInfo
    Snapshot-->>Main: updated process list with cpuPercent
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • manaflow-ai/cmux#3290: Modifies the same top/process snapshot subsystem and related helpers; overlaps with the new sampling sources.
  • manaflow-ai/cmux#3471: Prior work on scope cache and locking semantics related to this PR's cache refactor.

Poem

🐰 I count the ticks with careful paws,

Sampling clocks and guarding laws.
Locks held snug, deltas made true,
No more zero rows in view.
Hops of joy — CPU% for you!


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 Actor Isolation ❌ Error CmuxWebContentProcessIdentifier.pid(for:) accesses WKWebView (main-thread-only UI API) without @MainActor annotation. This introduces Swift 6 actor isolation mistake flagged in review. Add @MainActor annotation to pid(for:) function in CmuxTopProcessDetails.swift. Also mark enum as nonisolated for consistency with codebase patterns.
Cmux Swift @Concurrent ❌ Error CmuxWebContentProcessIdentifier.pid(for:) accesses WKWebView without @MainActor, violating swift-concurrent-annotation.md rules for actor-isolated state access. Add @MainActor annotation to CmuxWebContentProcessIdentifier.pid(for:) in Sources/CmuxTopProcessDetails.swift line 5 to mark it main-thread-only.
Docstring Coverage ⚠️ Warning Docstring coverage is 3.70% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix Task Manager CPU samples' directly addresses the main change—replacing stale kinfo_proc.p_pctcpu with real CPU rate computation. It clearly summarizes the primary fix without being vague.
Linked Issues check ✅ Passed The PR fully addresses issue #3583 by replacing kinfo_proc.p_pctcpu with PROC_PIDTASKINFO-based CPU rate computation, adding per-process identity keying, locking, and regression tests for non-zero CPU and overflow handling.
Out of Scope Changes check ✅ Passed All changes are tightly scoped to CPU sampling and its supporting infrastructure. Lock refactoring and scope-cache modernization are necessary enablers for the CPU fix; no unrelated feature work or style-only changes are present.
Cmux Swift Blocking Runtime ✅ Passed Introduces OSAllocatedUnfairLock with documented justification for synchronous snapshot capture. No prohibited primitives (semaphores, sleep, main.sync). Test polling is deterministic and allowed.
Cmux Swift Concurrency ✅ Passed No legacy async patterns introduced. Modern OSAllocatedUnfairLock, proper Sendable/nonisolated types, no DispatchQueue/Combine/completion-handlers. Appropriate for modern Swift concurrency.
Cmux Swift File And Package Boundaries ✅ Passed New files under 400-line threshold with single responsibilities. Modifications don't exceed 250-line limits. No mixed concerns. Qualifies as focused bug fix per allowed cases.
Cmux Swift Logging ✅ Passed No logging violations detected. Code complies with swift-logging.md: no print/debugPrint/dump/NSLog, no ad hoc file/stdout logging, no improper Logger constants, and no sensitive data exposure.
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI imports, state declarations, Views, or render-time mutations. Changes are CPU tracking and process snapshot functionality using Foundation/Darwin. Check not applicable.
Cmux Architecture Rethink ✅ Passed Replaces broken CPU sampling with delta-based calculation. Locks are required for consistency, not symptom patches. Polling test-only. Clear ownership and invariants documented.
Description check ✅ Passed The PR description comprehensively covers the fix, root cause, implementation details, and testing validation. All major template sections are addressed with sufficient detail.
✨ 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-3583-task-manager-cpu-zero

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.

@greptile-apps

greptile-apps Bot commented May 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Replaces the always-zero kinfo_proc.p_pctcpu CPU source with a real rate computed from PROC_PIDTASKINFO cumulative ticks divided by a CLOCK_UPTIME_RAW monotonic delta, fixing the Task Manager and cmux top CPU% display. A new CmuxTopProcessCPUTracker owns per-process sample history inside a single OSAllocatedUnfairLock transaction.

  • New file CmuxTopProcessCPUTracker.swift: introduces CmuxTopProcessCPUTracker, a file-private singleton guarded by OSAllocatedUnfairLock; computes and commits CPU percentages in one critical section, keys samples by pid+start-time, guards against overflow sentinels (UInt64.max), and allows only the newest capture to prune inactive entries.
  • CmuxTopSnapshot.swift refactored: allProcesses now gathers CPU samples in a first pass, delegates delta computation to the tracker, then applies the returned map in a second pass; scope-cache pruning remains a separate step after the CPU transaction.
  • CmuxTopProcessDetails.swift extracted: moves CmuxWebContentProcessIdentifier into its own file; CmuxTopSnapshotScopeCache.swift migrated from NSLock to OSAllocatedUnfairLock with documented rationale; regression test CmuxTopProcessCPUTests added for overflow sentinel and busy-process cases.

Confidence Score: 5/5

Safe to merge — the CPU sampling path is self-contained, all new state is guarded by a documented lock, and the fix is well-scoped to the zero-CPU regression.

All new types carry explicit nonisolated annotations; the OSAllocatedUnfairLock usage is documented and constrained to arithmetic-only work inside the critical section; overflow sentinels are caught before any division; out-of-order capture protection is implemented correctly; and all prior review feedback has been fully addressed in this revision.

No files require special attention. CmuxTopProcessCPUTracker.swift is the highest-risk new file but is small, well-commented, and the lock invariant is narrow and documented.

Important Files Changed

Filename Overview
Sources/CmuxTopProcessCPUTracker.swift New file: implements CmuxTopProcessCPUTracker with OSAllocatedUnfairLock, overflow-sentinel guards, and out-of-order capture protection; all isolation annotations are explicit and correct.
Sources/CmuxTopSnapshot.swift Refactored allProcesses to two-pass CPU sampling (gather then apply); removes kinfo_proc.p_pctcpu; scope-cache pruning unchanged; logic is sound.
Sources/CmuxTopSnapshotScopeCache.swift Migrated scope cache lock from NSLock to OSAllocatedUnfairLock with documented rationale; all struct types explicitly nonisolated.
cmuxTests/CmuxTopProcessCPUTests.swift Adds overflow-sentinel unit test and busy-process integration test; RunLoop.current.run sleep is test-only and explicitly allowed by the blocking-runtime rule.
Sources/CmuxTopProcessDetails.swift New 17-line file extracting CmuxWebContentProcessIdentifier; @mainactor annotation on pid(for:) is correct for WKWebView access.
GhosttyTabs.xcodeproj/project.pbxproj Adds build file and file reference entries for the three new files; mechanically correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[allProcesses called] --> B[sysctl KERN_PROC_ALL]
    B --> C[Build activeScopeKeys from all kinfo_proc]
    C --> D[cpuSampleClockNanoseconds single CLOCK_UPTIME_RAW timestamp]
    D --> E{For each process}
    E --> F[proc_pidinfo PROC_PIDTASKINFO]
    F -->|success| G[cpuSample: ticks + timestamp to currentCPUSamples map]
    F -->|failure| H[cpuSampleKey = nil, memory = 0]
    G --> E
    H --> E
    E -->|done| I[CmuxTopProcessCPUTracker.cpuPercentages one OSAllocatedUnfairLock transaction]
    I --> J{For each key in currentSamples}
    J --> K{existing sample newer?}
    K -->|yes| L[skip preserve newer sample]
    K -->|no| M{either sample is UInt64.max?}
    M -->|yes| N[cpuPercent = 0]
    M -->|no| O[delta ticks x mach_timebase ratio divided by wall ns x 100]
    N --> P[state.samples update]
    O --> P
    P --> J
    J -->|done| Q{sampledAt >= latestPrunedAt?}
    Q -->|yes| R[prune state.samples to activeKeys]
    Q -->|no| S[skip prune older capture]
    R --> T[return percentages map]
    S --> T
    T --> U[Apply cpuPercent to each processRecord]
    U --> V[pruneCMUXScopeCache activeKeys]
    V --> W[Return CmuxTopProcessInfo array]
Loading

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

Comment thread Sources/CmuxTopProcessCPUTracker.swift Outdated
Comment thread Sources/CmuxTopProcessCPUTracker.swift Outdated
Comment thread Sources/CmuxTopProcessCPUTracker.swift
The CPU rate cache remains synchronous because it serves both the Task Manager refresh path and the sync system.top socket command, but the mutable history now has one small owner with lock-scoped reads and writes. Recording merges new samples before pruning stale ones so overlapping captures cannot discard newer state written by another caller.

Constraint: CmuxTopProcessSnapshot.capture is synchronous for socket and UI callers

Rejected: Convert capture to an actor API | would widen this bug fix across socket handlers and callers unrelated to the CPU-zero regression

Confidence: high

Scope-risk: narrow

Directive: Keep proc/sysctl sampling outside the sample-store critical section

Tested: git diff --check

Not-tested: Local XCTest per repository policy
The task-manager and v2 system.top paths share CmuxTopProcessSnapshot.capture, and one caller is intentionally synchronous. The CPU history is now owned by a dedicated tracker that computes percentages and commits samples in one locked transaction, removing the previous read-then-record split that could race between overlapping captures. Process detail helpers moved out of CmuxTopSnapshot so the guard budget stays under the threshold without accepting new debt.

Constraint: CmuxTopProcessSnapshot.capture still backs the synchronous v2 system.top socket path

Rejected: Actor-owned CPU sample store | would require making capture async and either blocking the socket worker or widening socket command execution beyond this PR

Confidence: high

Scope-risk: narrow

Directive: Keep proc/sysctl sampling outside the CPU tracker lock; only the sample history transaction belongs inside the owner

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check

Not-tested: Full app build deferred until CI is green, then ./scripts/reload.sh --tag issue-3583-task-manager-cpu-zero --launch
Overlapping top captures can complete in a different order than they sampled. The CPU tracker now stores both sample history and the latest prune timestamp, so an older capture can update missing older samples but cannot evict entries from a newer completed capture.

Constraint: Keep CmuxTopProcessSnapshot.capture synchronous for the v2 system.top socket path

Rejected: Rely on sampledAtNanoseconds > during filter | hid the out-of-order invariant inside an impossible-looking branch

Confidence: high

Scope-risk: narrow

Directive: Pruning must remain monotonic by sample time; do not let older captures evict newer sample history

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check

Not-tested: Full app build deferred until CI is green, then ./scripts/reload.sh --tag issue-3583-task-manager-cpu-zero --launch
coderabbitai[bot]
coderabbitai Bot previously requested changes May 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@GhosttyTabs.xcodeproj/project.pbxproj`:
- Line 244: The PBXBuildFile entry with ID C7A50C000000000000002 references a
nonexistent PBXFileReference ID C7A50C000000000001; update that build file's
fileRef to the actual file reference ID C7A50C000000000000001 (the
PBXFileReference created for CmuxTopProcessCPUTests.swift) so the
CmuxTopProcessCPUTests.swift build file is correctly wired into the project
(locate the PBXBuildFile entry for C7A50C000000000000002 and replace its fileRef
value).

In `@Sources/CmuxTopProcessCPUTracker.swift`:
- Around line 27-37: The code computes cpuPercent using storedSamples[key]
before checking whether the incoming sample is actually newer, which lets a
later-sampled-but-earlier-committed sample produce 0% and prevent the rightful
newer sample from being stored; fix by first reading storedSamples[key] into a
local (e.g., let existing = storedSamples[key]), perform the recency guard (if
let existing = existing, existing.sampledAtNanoseconds >
sample.sampledAtNanoseconds { continue }), then compute percentages[key] =
CmuxTopProcessSnapshot.cpuPercent(current: sample, previous: existing) and
finally assign storedSamples[key] = sample if the sample is newer, ensuring
cpuPercent uses the correct previous snapshot and updates happen in the correct
order.

In `@Sources/CmuxTopProcessDetails.swift`:
- Around line 32-37: The fixedString<T>(_:) helper uses String(cString:) which
can read past the passed rawBuffer if there is no NUL inside the fixed-width C
buffer; change it to scan the bytes returned by withUnsafeBytes(rawBuffer) /
chars (bound to CChar) for the first zero within rawBuffer.count, get the
slice/length up to that index (or rawBuffer.count if none), then construct a
Swift String from those bytes (or use String(bytes:encoding:)) and trim
whitespace; update references in fixedString, withUnsafeBytes, rawBuffer, chars,
and baseAddress to ensure you only decode up to that located NUL to avoid
out-of-bounds reads.
🪄 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: cdc6e803-f154-4558-8f6f-6ab610493c25

📥 Commits

Reviewing files that changed from the base of the PR and between aea6cfc and f3d3b8c.

📒 Files selected for processing (5)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/CmuxTopProcessCPUTracker.swift
  • Sources/CmuxTopProcessDetails.swift
  • Sources/CmuxTopSnapshot.swift
  • cmuxTests/CmuxTopProcessCPUTests.swift

Comment thread GhosttyTabs.xcodeproj/project.pbxproj Outdated
Comment thread Sources/CmuxTopProcessCPUTracker.swift Outdated
Comment thread Sources/CmuxTopProcessDetails.swift Outdated
Code review found one broken PBX file reference and two unsafe CPU sampling edges. The tracker now ignores stale overlapping samples before calculating rates, and fixed-width process strings decode only inside their known buffer bounds.

Constraint: PR review requires every actionable CodeRabbit thread to be addressed before the final tagged reload

Confidence: high

Scope-risk: narrow

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: plutil -lint GhosttyTabs.xcodeproj/project.pbxproj

Tested: git diff --check
@lawrencecchen
lawrencecchen dismissed coderabbitai[bot]’s stale review May 6, 2026 07:34

Dismissed as obsolete after follow-up fixes. All three CodeRabbit inline threads were fixed in commit 5ca62d7, replied to, resolved, and CodeRabbit subsequently acknowledged the fixes; the latest CodeRabbit status check is passing.

Comment thread Sources/CmuxTopProcessCPUTracker.swift Outdated
Greptile flagged that the lock-owned CPU tracker state should carry the same explicit isolation intent as the CPU sample value. The state remains synchronously owned by OSAllocatedUnfairLock; this change documents that the value type itself is not MainActor-isolated.

Constraint: Post-merge review feedback requested an annotation-only Swift fix

Confidence: high

Scope-risk: narrow

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check
The process snapshot path is not UI-owned: Task Manager samples it through a detached task and system.top samples it from a socket-worker path. Under MainActor default isolation, helper extensions and module globals still inherited ambient main-actor isolation even though their state is protected by process-snapshot locks.

This marks the process snapshot value domain and helper extensions nonisolated, makes the CPU tracker Sendable conformance explicit, and moves the scope cache from NSLock plus a mutable global to an OSAllocatedUnfairLock-owned state value. That keeps the synchronous API intact while making the non-UI ownership boundary explicit to Swift 6 isolation checks.

Constraint: Do not make system.top async; it is a synchronous socket-worker RPC path.

Rejected: Convert capture to an actor | would force async plumbing through the sync socket response path and add actor hops around a tiny dictionary transaction.

Rejected: Use nonisolated(unsafe) on mutable globals | OSAllocatedUnfairLock expresses the synchronization boundary directly.

Confidence: high

Scope-risk: narrow

Directive: Keep proc/sysctl/WebKit process inspection outside lock critical sections; locks own cache/history dictionaries only.

Tested: swiftc -default-isolation MainActor -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check

Not-tested: xcodebuild and local test bundles, per repo policy and task instruction.
Greptile's final review noted that the two-pass CPU snapshot flow was correct but carried a fragile parallel-array join, and that the mach_timebase_info fallback deserved a second look. This collapses the join into a single tuple record list and makes timebase conversion fail closed to 0% when the system ratio cannot be loaded.

Constraint: Preserve the existing synchronous capture API and shared system.top payload.

Rejected: Leave the observations as comments | the tuple record shape is a smaller and more durable invariant than explaining parallel arrays.

Rejected: Keep ratio fallback at 1 | that can report plausible but incorrect CPU if mach_timebase_info fails.

Confidence: high

Scope-risk: narrow

Directive: Keep each process info paired with its CPU sample key until CPU percentages are applied; do not reintroduce parallel arrays for this path.

Tested: swiftc -default-isolation MainActor -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check

Not-tested: xcodebuild and local test bundles, per repo policy and task instruction.
Greptile's last review found that extracting process-detail helpers widened private snapshot construction utilities to module-internal access. The helper calls are only needed by the private processInfo path, so keep them private in the snapshot file and move the already-internal cmux scope parsing beside the scope cache that consumes it.

Constraint: Do not touch YAML files while updating this PR

Rejected: Leave helpers internal | preserves an avoidable API-surface expansion

Confidence: high

Scope-risk: narrow

Directive: Keep low-level proc/task decoding helpers scoped to snapshot construction unless a real external caller appears

Tested: swiftc -default-isolation MainActor -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check
coderabbitai[bot]
coderabbitai Bot previously requested changes May 7, 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

Caution

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

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

33-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent PID-reuse misattribution on cache miss

On a miss, scope is fetched via cmuxScope(for: pid) (PID-only) and then cached under a (pid + start time) key. If the PID is reused between sampling and this lookup, you can cache the wrong process scope under the old identity.

Proposed fix
 static func cachedCMUXScope(
     for pid: Int,
     cacheKey: CmuxTopProcessScopeCacheKey
 ) -> CmuxTopProcessScope? {
     if let cached = cmuxTopScopeCache.withLock({ cache in cache[cacheKey] }) {
         return cached.scope
     }

+    // Re-validate identity before resolving/caching by PID.
+    guard let current = kinfoProc(for: pid),
+          scopeCacheKey(from: current) == cacheKey else {
+        return nil
+    }
+
     guard let scope = cmuxScope(for: pid) else {
         return nil
     }

     cmuxTopScopeCache.withLock { cache in
         cache[cacheKey] = CmuxTopProcessScopeCacheValue(scope: scope)
     }

     return 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/CmuxTopSnapshotScopeCache.swift` around lines 33 - 47, The
cachedCMUXScope(for:cacheKey:) function fetches a scope with cmuxScope(for: pid)
that may belong to a different process if the PID was recycled; to fix, after
obtaining scope verify the process identity matches the cache key (e.g. compare
scope.startTime or other unique identity field to cacheKey.startTime) and only
insert into cmuxTopScopeCache via cmuxTopScopeCache.withLock { ... } when they
match; if they don't match, return nil (and do not cache). Also ensure the
initial cache read still returns only when cached.scope's identity matches
cacheKey before returning.
🤖 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/CmuxTopProcessDetails.swift`:
- Around line 5-14: Mark the pid(for:) helper as main-actor-isolated by adding
the `@MainActor` attribute to its declaration (i.e. change the signature of static
func pid(for webView: WKWebView) -> Int? to be `@MainActor` static func pid(for
webView: WKWebView) -> Int?) so all access to the main-thread-only WKWebView API
is constrained to the main actor; after changing the signature, update any
callers invoked from non-main contexts to await/dispatch to the main actor
(e.g., call from taskManagerTopPayload or other background code via Task {
`@MainActor` in ... } or await MainActor.run { ... }) so the function is never
invoked off the main thread.

---

Outside diff comments:
In `@Sources/CmuxTopSnapshotScopeCache.swift`:
- Around line 33-47: The cachedCMUXScope(for:cacheKey:) function fetches a scope
with cmuxScope(for: pid) that may belong to a different process if the PID was
recycled; to fix, after obtaining scope verify the process identity matches the
cache key (e.g. compare scope.startTime or other unique identity field to
cacheKey.startTime) and only insert into cmuxTopScopeCache via
cmuxTopScopeCache.withLock { ... } when they match; if they don't match, return
nil (and do not cache). Also ensure the initial cache read still returns only
when cached.scope's identity matches cacheKey before returning.
🪄 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: 41bf6767-1dfc-44f6-bc71-8d1ebb2bc1a6

📥 Commits

Reviewing files that changed from the base of the PR and between d3fb088 and fbe5eeb.

📒 Files selected for processing (4)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Sources/CmuxTopProcessDetails.swift
  • Sources/CmuxTopSnapshot.swift
  • Sources/CmuxTopSnapshotScopeCache.swift

Comment thread Sources/CmuxTopProcessDetails.swift
Review found that a PID-only procargs lookup could run after the sampled PID had been recycled, then cache the replacement process scope under the old pid+start-time identity. The cache miss path now resolves procargs only when the current kinfo_proc identity matches before and after the lookup, and the WebKit PID bridge is explicitly main-actor isolated.

Constraint: Scope cache is intentionally synchronous because top snapshots are used by both async UI sampling and sync socket paths

Rejected: Cache PID-only procargs results without revalidation | can misattribute CMUX scope after PID reuse

Confidence: high

Scope-risk: narrow

Directive: Do not cache process-derived scope data unless the pid+start-time identity still matches the sampled key

Tested: swiftc -default-isolation MainActor -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: swiftc -typecheck Sources/CmuxTopSnapshot.swift Sources/CmuxTopSnapshotScopeCache.swift Sources/CmuxTopProcessCPUTracker.swift Sources/CmuxTopProcessDetails.swift

Tested: python3 scripts/swift_file_length_budget.py --budget .github/swift-file-length-budget.tsv

Tested: git diff --check
@lawrencecchen
lawrencecchen dismissed coderabbitai[bot]’s stale review May 7, 2026 01:34

Stale CodeRabbit changes-requested review from fbe5eeb. The requested fixes were addressed in 7c71267/af82466a1: the WebKit PID helper is @mainactor and scope-cache misses revalidate pid+start-time identity before caching. Latest checks are green, CodeRabbit status is success, Greptile reviewed af82466 as safe, and all review threads are resolved.

@austinywang
austinywang merged commit 9b8799b into main May 7, 2026
24 checks passed

This branch was successfully deployed

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

Task Manager: CPU column always shows 0.0% (also affects cmux top)

1 participant