Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 2ccf44c

Browse files
committed
Filter out child Claude processes to show only root instances
- Add parent PID (ppid) tracking to process info - Collect all Claude processes in first pass - Filter out any Claude whose parent is also a Claude process - Only show root Claude instances (not MCP services or sub-processes) This fixes the issue where multiple child processes spawned by Claude (like MCP services) were being shown as separate instances. Now only the main Claude processes are displayed.
1 parent bd41ebf commit 2ccf44c

1 file changed

Lines changed: 23 additions & 7 deletions

File tree

Features/Monitoring/Domain/Services/ClaudeProcessDetector.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ final class ClaudeProcessDetector: Loggable, @unchecked Sendable {
5757
let totalProcesses = size / Int32(MemoryLayout<pid_t>.size)
5858
logger.debug("Scanning \(totalProcesses) processes for Claude instances")
5959

60-
var instances: [ClaudeInstance] = []
60+
var allClaudes: [(instance: ClaudeInstance, ppid: pid_t)] = []
61+
var claudePIDs = Set<pid_t>()
6162
var nodeProcessCount = 0
6263

64+
// First pass: collect all Claude processes
6365
for i in 0..<totalProcesses {
6466
let pid = pids[Int(i)]
6567

@@ -103,12 +105,26 @@ final class ClaudeProcessDetector: Loggable, @unchecked Sendable {
103105
currentActivity: .idle
104106
)
105107

106-
instances.append(instance)
107-
logger.info("Detected Claude instance: PID=\(pid), folder=\(folderName), status=\(status.displayName)")
108+
allClaudes.append((instance: instance, ppid: processInfo.ppid))
109+
claudePIDs.insert(pid)
110+
logger.debug("Found Claude process: PID=\(pid), PPID=\(processInfo.ppid), folder=\(folderName)")
108111
}
109112

110-
logger.info("Detection complete: found \(nodeProcessCount) Node processes, \(instances.count) Claude instances")
111-
return instances
113+
// Second pass: filter out child Claude processes
114+
let rootClaudes = allClaudes.compactMap { item in
115+
let isChildOfClaude = claudePIDs.contains(item.ppid)
116+
117+
if isChildOfClaude {
118+
logger.debug("Filtering out child Claude process PID=\(item.instance.pid) (parent PID=\(item.ppid) is also Claude)")
119+
return nil
120+
} else {
121+
logger.info("Detected root Claude instance: PID=\(item.instance.pid), folder=\(item.instance.folderName), status=\(item.instance.status.displayName)")
122+
return item.instance
123+
}
124+
}
125+
126+
logger.info("Detection complete: found \(nodeProcessCount) Node processes, \(allClaudes.count) total Claude processes, \(rootClaudes.count) root Claude instances")
127+
return rootClaudes
112128
}
113129

114130
// MARK: - Helper Functions
@@ -123,7 +139,7 @@ final class ClaudeProcessDetector: Loggable, @unchecked Sendable {
123139

124140
// MARK: - Process Information Extraction
125141

126-
private func getProcessInfo(pid: pid_t) -> (command: String, device: dev_t)? {
142+
private func getProcessInfo(pid: pid_t) -> (command: String, device: dev_t, ppid: pid_t)? {
127143
var info = proc_bsdinfo()
128144

129145
guard proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, Int32(MemoryLayout.size(ofValue: info))) > 0 else {
@@ -146,7 +162,7 @@ final class ClaudeProcessDetector: Loggable, @unchecked Sendable {
146162
logger.warning("Device \(info.e_tdev) is too large to convert to Int32")
147163
device = 0 // Use 0 to indicate no valid device
148164
}
149-
return (command: command, device: device)
165+
return (command: command, device: device, ppid: pid_t(info.pbi_ppid))
150166
}
151167

152168
private func getProcessArguments(pid: pid_t) -> String? {

0 commit comments

Comments
 (0)