Repository navigation
fix(mcp): stop idle window tracking from burning CPU on app churn - #1013
Conversation
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review blockedAutomated review did not run, so no review verdict was produced. Reason: The input-safety check could not safely complete for this revision. Automatic review is on hold, including after source changes. Request a fresh re-review after resolving the failure; maintainers can also release the hold through an intentional scanner-policy update. Next step: No contributor action is requested. Maintainers should inspect the linked workflow run. |
PR SummaryLow Risk Overview
The visible diff here is the changelog entry (~0.68s → ~0.02s CPU per unrelated launch+quit at ~600 apps); the functional change is the dependency pointer. Reviewed by Cursor Bugbot for commit 2f5330f. Bugbot is set up for automated code reviews on this repo. Configure here. |
Move the AXorcist submodule to the signed v0.2.2 tag and the public root Package.swift pin from exact 0.2.1 to exact 0.2.2 so SwiftPM consumers of Peekaboo's library products get the idle-observer CPU fix (#1005, #1013); align docs/building.md and the SwiftPM consumer contract. Verification: scripts/test-swiftpm-consumer.sh resolved 0.2.2 from the published tag and built all four public products; macOS CI green (CLI, Core, Tachikoma, app builds, SwiftFormat, SwiftLint).
Fixes #1005.
Summary
An idle
peekaboo mcpserver burned CPU whenever any unrelated application launched or quit: about 0.5–0.7 s per launch+quit on a Mac with ~600 running applications. Each server pays this separately, and so does any other host runningWindowTrackerService's eight global AX watchers.The cost was in AXorcist's
AXWorkspaceApplicationMonitor, which is fixed upstream in openclaw/AXorcist#96 (merged as700e5ac). This PR bumps the submodule to that commit and adds the changelog entry. Two independent causes:NSRunningApplication.hashis constant. Every running app reports the same hash (verified: 92 apps → 1 distinct value). Sets and dictionaries keyed byNSRunningApplicationtherefore did a linearisEqual:/_LSCompareASNsscan per operation, making each snapshot O(N²). The reporter's release-binary samples point straight at this. AXorcist now indexes entries by the identity of the retained wrapper (NSWorkspace returns the same objects across snapshots) and falls back toisEqual:only for unseen wrappers.processIdentifieron every snapshot then cost a cold LaunchServices lookup per app. A Time Profiler trace attributed 75% of the remaining CPU to this. AXorcist now skips the read for a wrapper it already resolved, while new or equal-but-distinct wrappers are still read, so PID-transition, readiness and replacement contracts are unchanged.Window tracking for newly launched applications is unchanged: new wrappers are always read and delivered as launches, and AXorcist's tests assert this, including for processes that never report finished launching. No observers are dropped.
Measurements
Release builds of Peekaboo, idle
peekaboo mcp --no-remoteafter the MCP handshake, ~594 running applications, and 60 short-lived titled processes launched and quit (the issue's methodology). Two interleaved rounds:main700e5ac)Idle CPU without churn is unchanged, at about 0.2–0.3 s per 30 s. That meets the issue's restoration check of at most about 3 s for 120 launches.
Notes
Package.swiftstill pins AXorcistexact: "0.2.1"for external SwiftPM consumers, andscripts/test-swiftpm-consumer.shasserts that pin. External consumers such as OpenClaw get the fix once AXorcist 0.2.2 is tagged and the pin is aligned at the next release, following the precedent of build(deps): align consumers on AXorcist 0.2.1 #925. Peekaboo's own CLI, MCP server and app build from the submodule, so they get it now.swift test(294 tests),make check, and CI on Swift 6.2.4 and 6.4.0 all pass. The constant-hash and no-reread regressions fail on the old code. Codex autoreview was scoped-clean on each commit.