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

Commit bd41ebf

Browse files
committed
Fix Claude detection by properly parsing KERN_PROCARGS2 format
- Parse process arguments correctly, handling null-separated components - Look for 'claude' as a standalone argument after the executable path - Handle both direct 'claude' commands and 'node claude' invocations - Skip empty components and argc bytes at the beginning The format is: [4 bytes argc][executable\0][padding\0s][arg0\0][arg1\0]... This fixes detection of Claude processes that show as 'node claude' in ps.
1 parent 0beb202 commit bd41ebf

1 file changed

Lines changed: 29 additions & 6 deletions

File tree

Features/Monitoring/Domain/Services/ClaudeProcessDetector.swift

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,37 @@ final class ClaudeProcessDetector: Loggable, @unchecked Sendable {
206206
private func isClaudeProcess(arguments: String) -> Bool {
207207
let lowercasedArgs = arguments.lowercased()
208208

209-
// If the first argument (command) ends with "claude", it's definitely Claude
210-
let components = arguments.split(separator: "\0", maxSplits: 2, omittingEmptySubsequences: false)
211-
if let firstArg = components.first {
212-
let command = String(firstArg).trimmingCharacters(in: .whitespaces)
213-
if command.hasSuffix("claude") || command.hasSuffix("/claude") {
214-
logger.debug("Found Claude via command name: \(command)")
209+
// Parse the KERN_PROCARGS2 format
210+
// Format: [4 bytes argc][executable path\0][padding\0s][arg0\0][arg1\0]...
211+
let components = arguments.split(separator: "\0", omittingEmptySubsequences: false)
212+
213+
// Skip the first component (contains argc bytes) and look for the actual arguments
214+
for (index, component) in components.enumerated() {
215+
let componentStr = String(component).trimmingCharacters(in: .whitespaces)
216+
217+
// Skip empty components and the executable path
218+
if componentStr.isEmpty || index == 0 { continue }
219+
220+
// Check if this component is "claude" or ends with "/claude"
221+
if componentStr == "claude" || componentStr.hasSuffix("/claude") {
222+
logger.debug("Found Claude via argument: \(componentStr)")
215223
return true
216224
}
225+
226+
// Also check the executable path itself
227+
if componentStr.contains("/bin/node") && index + 1 < components.count {
228+
// Check the next non-empty component after node
229+
for nextIndex in (index + 1)..<components.count {
230+
let nextComponent = String(components[nextIndex]).trimmingCharacters(in: .whitespaces)
231+
if !nextComponent.isEmpty {
232+
if nextComponent == "claude" || nextComponent.hasSuffix("/claude") {
233+
logger.debug("Found Claude as node argument: \(nextComponent)")
234+
return true
235+
}
236+
break // Only check the first non-empty argument after node
237+
}
238+
}
239+
}
217240
}
218241

219242
// Check for specific Claude installation path patterns

0 commit comments

Comments
 (0)