Skip to content

fix: preserve PortScanner burst lifecycle - #11180

Closed
lawrencecchen wants to merge 4 commits into
mainfrom
fix-11109-port-scanner-wave123
Closed

lawrencecchen wants to merge 4 commits into
mainfrom
fix-11109-port-scanner-wave123

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #11109.

This branch first adds a regression test proving that unregistering one panel does not cancel another panel's active burst, then fixes the scheduler. It keeps one future burst callback instead of retaining completed work items, cancels the global scheduler only after the final panel is removed, and avoids restarting a scan after the final panel is gone. The lifecycle test uses queue and invocation signals instead of a fixed synchronization sleep.

Commits:

  • 4e6ba98 test: preserve other panel burst on unregister
  • 89ce4a4 fix: bound PortScanner burst lifecycle

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes the PortScanner burst lifecycle so unregistering one panel no longer cancels another panel's active burst scan.

  • Adds a regression test proving one panel's burst survives another panel's unregister.
  • Keeps a single future burst callback and invalidates stale scheduled work when the lifecycle changes.
  • Cancels the global burst scheduler only after the final panel is removed.
  • Avoids restarting a scan after the final panel is gone.
  • Chunks large lsof PID scans into batches of 256.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved port scanning for large PID sets by processing them in manageable batches and combining results accurately.
    • Prevented outdated background scans from running after panels are removed.
    • Preserved active scans when other registered panels remain.
  • Tests

    • Added coverage for panel lifecycle changes, scan cancellation, and continued scan activity.

@vercel

vercel Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux166 Ready Ready Preview Aug 29, 2026 11:19am
cmux41 Ready Ready Preview Aug 29, 2026 11:19am

@cursor

cursor Bot commented Aug 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 64c41f90-0deb-433b-af5a-c240cfcff9c1

📥 Commits

Reviewing files that changed from the base of the PR and between 3a76082 and 89ce4a4.

📒 Files selected for processing (3)
  • Sources/PortScanner+Process.swift
  • Sources/PortScanner.swift
  • cmuxTests/PortScannerTests.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

runLsof now batches PID queries above 256 entries. Port scanner burst scheduling now invalidates stale callbacks when panels unregister and preserves active bursts for remaining panels. Lifecycle tests cover both cancellation and preservation behavior.

Changes

lsof PID batching

Layer / File(s) Summary
Chunked lsof execution
Sources/PortScanner+Process.swift
runLsof splits PID lists larger than 256 entries, invokes runLsofChunk for each chunk, and merges the results. Smaller lists use the same single-query path.

Burst lifecycle scheduling

Layer / File(s) Summary
Burst invalidation and continuation
Sources/PortScanner.swift
PortScanner stores one deferred work item and uses a generation counter to reject stale callbacks. Unregistering the final panel cancels pending scheduling, while other panels keep active bursts running.
Lifecycle behavior validation
cmuxTests/PortScannerTests.swift
Lifecycle tests verify that pending bursts do not invoke commands after the final panel unregisters and that another panel preserves continued invocations.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to 89ce4

The PR preserves active bursts when panels unregister, but its batched process scan treats any failed batch as making the entire scan incomplete and can lengthen scans as the process list grows, potentially leaving valid port results missing across panels or workspaces. Merge should wait for this behavior to be fixed or explicitly accepted.

Suggested reviewers: austinywang, mykmelez

Sequence Diagram(s)

sequenceDiagram
  participant Panel
  participant PortScanner
  participant DispatchWorkItem
  participant CommandRunner

  Panel->>PortScanner: register and kick scan
  PortScanner->>DispatchWorkItem: schedule coalesced burst
  Panel->>PortScanner: unregister panel
  PortScanner->>DispatchWorkItem: cancel pending work
  DispatchWorkItem->>PortScanner: attempt deferred callback
  PortScanner->>PortScanner: reject stale generation
  PortScanner->>CommandRunner: continue commands only for remaining panels
Loading

Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 2 warnings)

Check name Status Explanation Resolution
Cmux Swift @Concurrent ❌ Error The diff introduces runLsofChunk(pidsCsv:) async and materially expands runLsof(pidsCsv:) async. These functions execute /usr/sbin/lsof and parse and merge potentially large output, but neither … Add the repository's compiler-conditional @concurrent annotation to the new process-scanning async boundary, with nonisolated as needed, or move the lsof execution and parsing into an explicitly concurrent worker. Keep actor-isolated st…
Cmux Swift Package Boundaries ❌ Error The PR adds independent lsof process-scanning logic in the app target. Sources/PortScanner+Process.swift now parses PID CSV input, batches it into 256-PID chunks, and merges scan results. The file… Create a small macOS SwiftPM target named CmuxPortScanning. Move the lsof result model and the runLsof/runLsofChunk boundary, including PID chunking, output parsing, completeness, and result merging, into that target. Expose `LsofScan…
Description check ⚠️ Warning The description explains the changes and motivation, but it omits the required Testing, Demo Video, Review Trigger, and Checklist sections. Add the required template sections. Document local and manual testing, include a demo video or state why one is not applicable, add the review-trigger block, and complete the checklist.
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (21 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: preserving the PortScanner burst lifecycle.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: The production diff adds no implicit MainActor model or service protocol, and it does not access a UI-bound store from a background context. PortScanner was already @unchecked Sendable; its …
Cmux Swift Blocking Runtime ✅ Passed No prohibited blocking primitive is introduced in production Swift. The review-base PortScanner already used queue.asyncAfter; the PR wraps that existing callback in one cancellable `DispatchWorkI…
Cmux Browser Automation Off-Main ✅ Passed PASS: The complete pull-request range changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. It does not change browser socket automatio…
Cmux Expensive Synchronous Load ✅ Passed The PR does not add or move an agent-history load. The production diff only adds PID chunking for asynchronous lsof scans and burst lifecycle scheduling. It adds no `RestorableAgentSessionIndex.load…
Cmux Cache Substitution Correctness ✅ Passed PASS: The production diff does not replace a fresh authoritative read with a cache. runLsof still invokes /usr/sbin/lsof through commandRunner; the new code only chunks and merges fresh results.…
Cmux No Hacky Sleeps ✅ Passed PASS: The pull request changes only Swift files (Sources/PortScanner.swift, Sources/PortScanner+Process.swift, and cmuxTests/PortScannerTests.swift). This check covers non-Swift production runti…
Cmux Algorithmic Complexity ✅ Passed No algorithmic-complexity failure is introduced. The new runLsof wrapper parses the PID list once and processes each PID in explicit chunks of at most 256. The merge traverses each chunk result once…
Cmux Swift Concurrency ✅ Passed PASS. The PR does not introduce a custom background queue, Combine state, or a new completion-handler API. PortScanner.queue and the production Task calls already existed in the base revision. The…
Cmux Swiftpm Lockfiles ✅ Passed PASS. The complete PR range from 699f1a7 to 89ce4a4 changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. No Package.swift, Package.resol…
Cmux Swift Logging ✅ Passed PASS. The changed production Swift code adds lsof chunking and burst lifecycle scheduling only. The added lines contain no print, debugPrint, dump, NSLog, ad hoc logging, Logger declarations, …
Cmux User-Facing Error Privacy ✅ Passed PASS. The production diff only changes internal lsof chunking and PortScanner scheduling. It adds no user-facing errors, alerts, command output, API error bodies, or recovery copy. lsof and ps d…
Cmux Full Internationalization ✅ Passed PASS: The complete PR diff changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. Production additions contain PID parsing, scheduling l…
Cmux Swiftui State Layout ✅ Passed PASS: The PR changes only PortScanner process/scheduling code and XCTest-style lifecycle support. The changed files contain no SwiftUI views, ObservableObject or property-wrapper state, GeometryReader…
Cmux Architecture Rethink ✅ Passed PASS. The production diff is a local scheduler correctness fix in PortScanner. It keeps all scheduler state on the existing serial queue, replaces the pre-existing work-item array with one future …
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The pull request changes only PortScanner process/scheduling code and PortScanner test infrastructure. The aggregate diff introduces no NSWindow, NSPanel, NSWindowController, SwiftUI Window/Wind…
Cmux Source Artifacts ✅ Passed PASS: The aggregate pull-request diff changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. These are intentional hand-written source a…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS. The PR changes only Sources/PortScanner.swift and Sources/PortScanner+Process.swift in production. Added members are private implementation details: burstGeneration, `scheduledBurstWorkIte…
Cmux No Ambient Global State ✅ Passed PASS. The production diff adds only PortScanner instance state (private var burstGeneration and private var scheduledBurstWorkItem) and private instance methods (runLsofChunk, runBurst, and …
Full details: Cmux Swift Actor Isolation

Explanation

PASS: The production diff adds no implicit MainActor model or service protocol, and it does not access a UI-bound store from a background context. PortScanner was already @unchecked Sendable; its new scheduler state remains protected by the existing serial queue, as documented by the class state comment. The changed lsof code uses the existing Sendable CommandRunning protocol and PortLsofScanResult. The added actor-isolation-sensitive test code is test-only and includes an actor, which the check allows.

Full details: Cmux Swift Blocking Runtime

Explanation

No prohibited blocking primitive is introduced in production Swift. The review-base PortScanner already used queue.asyncAfter; the PR wraps that existing callback in one cancellable DispatchWorkItem, adds generation checks, and reduces retained callbacks. It does not add a new delay or materially expand timing synchronization. The lsof chunk loop uses async awaits only. The added Task.sleep is test-only timeout scaffolding, and the tests also use AsyncStream and continuations for explicit signals.

Full details: Cmux Browser Automation Off-Main

Explanation

PASS: The complete pull-request range changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. It does not change browser socket automation, TerminalController.swift, the execution policy, or policy tests. The custom check is therefore inapplicable.

Full details: Cmux Expensive Synchronous Load

Explanation

The PR does not add or move an agent-history load. The production diff only adds PID chunking for asynchronous lsof scans and burst lifecycle scheduling. It adds no RestorableAgentSessionIndex.load(), agent hook/session-store read, transcript/trajectory/workstream JSON parsing, directory scan, or synchronous file API. runLsof is reached from async scan tasks, and CommandRunning.run is an async off-main subprocess boundary. The existing per-PID identity checks remain in the moved runLsofChunk body and are not worsened by the PR. The new lifecycle tests are test-only code.

Full details: Cmux Cache Substitution Correctness

Explanation

PASS: The production diff does not replace a fresh authoritative read with a cache. runLsof still invokes /usr/sbin/lsof through commandRunner; the new code only chunks and merges fresh results. The PortScanner.swift changes only manage burst scheduling and in-memory scan state. No persistence, history, undo, or snapshot consumer changes, and no cold or stale cache path was introduced.

Full details: Cmux No Hacky Sleeps

Explanation

PASS: The pull request changes only Swift files (Sources/PortScanner.swift, Sources/PortScanner+Process.swift, and cmuxTests/PortScannerTests.swift). This check covers non-Swift production runtime changes. The added Task.sleep is in test-only timeout scaffolding, which the rule allows. Swift timing primitives are covered by the separate Swift blocking-runtime check.

Full details: Cmux Algorithmic Complexity

Explanation

No algorithmic-complexity failure is introduced. The new runLsof wrapper parses the PID list once and processes each PID in explicit chunks of at most 256. The merge traverses each chunk result once and unions sets, so the added aggregation is linear in the requested PIDs and reported ports. The six burst offsets are a fixed-size collection, and the lifecycle changes add no scalable nested scan. Existing sorting and scan logic is unchanged.

Full details: Cmux Swift Concurrency

Explanation

PASS. The PR does not introduce a custom background queue, Combine state, or a new completion-handler API. PortScanner.queue and the production Task calls already existed in the base revision. The PR changes the existing burst asyncAfter callback to one stored, cancellable DispatchWorkItem; this bounds the existing scheduler lifecycle and does not materially expand ordinary background async work. The new AsyncStream, task group, and queue-drain helper are test-only synchronization. The runLsof change remains async/await code.

Full details: Cmux Swift `@Concurrent`

Explanation

The diff introduces runLsofChunk(pidsCsv:) async and materially expands runLsof(pidsCsv:) async. These functions execute /usr/sbin/lsof and parse and merge potentially large output, but neither function has an @concurrent boundary. This violates the rule for nonisolated async work that should leave the caller actor. The scheduler changes add no invalid @concurrent usage.

Resolution

Add the repository's compiler-conditional @concurrent annotation to the new process-scanning async boundary, with nonisolated as needed, or move the lsof execution and parsing into an explicitly concurrent worker. Keep actor-isolated state access outside that worker.

Full details: Cmux Swift Package Boundaries

Explanation

The PR adds independent lsof process-scanning logic in the app target. Sources/PortScanner+Process.swift now parses PID CSV input, batches it into 256-PID chunks, and merges scan results. The file imports CmuxCore, CmuxFoundation, Darwin, and Foundation, but no AppKit, SwiftUI, Ghostty, or panel UI state. The logic uses the injected CommandRunning seam and is separate from the panel lifecycle. PortLsofScanResult also remains in the app-root Sources/ path, while existing CmuxCore and CmuxFoundation packages already provide related port and command abstractions. The scheduler changes are app-lifecycle composition and are allowed, but the new lsof scanning expansion matches the rule's independent domain-logic condition.

Resolution

Create a small macOS SwiftPM target named CmuxPortScanning. Move the lsof result model and the runLsof/runLsofChunk boundary, including PID chunking, output parsing, completeness, and result merging, into that target. Expose LsofScanning or LsofPortScanner as the first public API. Inject CommandRunning and process identity/presence checks. Keep panel registration, burst scheduling, callbacks, and publication joins in the app target. Add package-local tests for chunking and merged completeness, then link the package target to the app and test targets.

Full details: Cmux Swiftpm Lockfiles

Explanation

PASS. The complete PR range from 699f1a7 to 89ce4a4 changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. No Package.swift, Package.resolved, .gitignore, workflow, or Xcode project path changed. Therefore the PR introduces no SwiftPM dependency or Xcode package-reference change that requires a lockfile diff, and it does not introduce a cmux-owned .gitignore rule for Package.resolved.

Full details: Cmux Swift Logging

Explanation

PASS. The changed production Swift code adds lsof chunking and burst lifecycle scheduling only. The added lines contain no print, debugPrint, dump, NSLog, ad hoc logging, Logger declarations, or secret/personal-data logging. The stdout and stderr strings occur only in test CommandResult fixtures and are not logging statements.

Full details: Cmux User-Facing Error Privacy

Explanation

PASS. The production diff only changes internal lsof chunking and PortScanner scheduling. It adds no user-facing errors, alerts, command output, API error bodies, or recovery copy. lsof and ps diagnostics remain internal parser inputs, and results expose only port data through existing callbacks. The added comments and lifecycle tests are allowed by the rule.

Full details: Cmux Full Internationalization

Explanation

PASS: The complete PR diff changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. Production additions contain PID parsing, scheduling logic, and developer-only comments. They add no user-facing Swift text, localization keys, string-catalog entries, web messages, metadata, or rendered content. The test-only additions are explicitly allowed by the rule.

Full details: Cmux Swiftui State Layout

Explanation

PASS: The PR changes only PortScanner process/scheduling code and XCTest-style lifecycle support. The changed files contain no SwiftUI views, ObservableObject or property-wrapper state, GeometryReader, lazy/list row subtree, or render-time state mutation. The check is therefore not applicable.

Full details: Cmux Architecture Rethink

Explanation

PASS. The production diff is a local scheduler correctness fix in PortScanner. It keeps all scheduler state on the existing serial queue, replaces the pre-existing work-item array with one future callback, and cancels the scheduler only when ttyNames is empty. The diff does not add production sleeps, polling, locks, observers, side channels, duplicate action wiring, or split UI lifecycle ownership. asyncAfter and burstGeneration already existed before the PR. The new comments state the invariants and source of truth. The added Task.sleep and AsyncStream synchronization are test-only and allowed by the rule.

Full details: Cmux Swift Auxiliary Window Close Shortcuts

Explanation

PASS: The pull request changes only PortScanner process/scheduling code and PortScanner test infrastructure. The aggregate diff introduces no NSWindow, NSPanel, NSWindowController, SwiftUI Window/WindowGroup, close-shortcut routing, or auxiliary-window identifier code. The added test actor is a test-only fixture, which the rule allows. The auxiliary-window rule is therefore not applicable.

Full details: Cmux Source Artifacts

Explanation

PASS: The aggregate pull-request diff changes only Sources/PortScanner+Process.swift, Sources/PortScanner.swift, and cmuxTests/PortScannerTests.swift. These are intentional hand-written source and test files. No artifact directories, logs, screenshots, recordings, caches, build output, or dependency checkouts appear in the changed paths.

Full details: Cmux No Test Or Debug Seam In Production Source

Explanation

PASS. The PR changes only Sources/PortScanner.swift and Sources/PortScanner+Process.swift in production. Added members are private implementation details: burstGeneration, scheduledBurstWorkItem, runLsofChunk, and cancelBurstScheduling. The production diff adds no #if DEBUG, test-build guard, debug/test-named member, visibility widening, or test wrapper accessor. The test-only observation helpers remain in cmuxTests/PortScannerTests.swift and use existing scanner APIs such as queue and @testable import.

Full details: Cmux No Ambient Global State

Explanation

PASS. The production diff adds only PortScanner instance state (private var burstGeneration and private var scheduledBurstWorkItem) and private instance methods (runLsofChunk, runBurst, and cancelBurstScheduling). Sources/PortScanner+Process.swift places the new helper inside extension PortScanner. No new file-scope function, mutable global, static-only namespace, or singleton was added. The existing PortScanner.shared is identical at the PR base and head. Test-only declarations are outside the production Swift scope of this check.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-11109-port-scanner-wave123

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.

@lawrencecchen
lawrencecchen force-pushed the fix-11109-port-scanner-wave123 branch from 89ce4a4 to cd8293d Compare August 29, 2026 11:14
@cursor

cursor Bot commented Aug 29, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

The scheduler and bounded lsof handling from this follow-up are already covered on main by #11109 and subsequent PortScanner updates, so this PR is superseded. Closing it.

This branch was successfully deployed

2 active deployments
Preview – cmux166 — cd8293d0 Deployed Aug 29, 2026 by vercel[bot]
Preview – cmux41 — cd8293d0 Deployed Aug 29, 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.

2 participants