@@ -42,6 +42,8 @@ struct TerminalResolver: Sendable {
4242
4343 /// Resolve terminal using a pre-built process tree (avoids duplicate sysctl calls)
4444 nonisolated func resolve( claudePid: Int , tree: [ Int : ProcessInfo ] ) -> ResolvedTerminal ? {
45+ // Fetch running apps once for the entire resolve chain (single DispatchQueue.main.sync)
46+ let apps = Self . getRunningApps ( )
4547
4648 // Check if in tmux
4749 let isInTmux = ProcessTreeBuilder . shared. isInTmux ( pid: claudePid, tree: tree)
@@ -52,7 +54,7 @@ struct TerminalResolver: Sendable {
5254 // Walk up process tree to find terminal app
5355 if let appInfo = TerminalAppRegistry . appInfo ( forPid: claudePid, tree: tree) {
5456 // Verify the app is actually running and get its bundle ID
55- if let bundleId = findRunningBundleId ( for: appInfo) {
57+ if let bundleId = findRunningBundleId ( for: appInfo, apps : apps ) {
5658 return ResolvedTerminal (
5759 appInfo: appInfo,
5860 bundleId: bundleId,
@@ -65,24 +67,25 @@ struct TerminalResolver: Sendable {
6567 // If we're in tmux, the terminal might not be a direct parent.
6668 // Try to find it via tmux client PID.
6769 if isInTmux {
68- return resolveViaTmuxClient ( claudePid: claudePid, tree: tree, tty: tty)
70+ return resolveViaTmuxClient ( claudePid: claudePid, tree: tree, tty: tty, apps : apps )
6971 }
7072
7173 // Last resort: check running applications against known terminals
72- return resolveFromRunningApps ( tty: tty)
74+ return resolveFromRunningApps ( tty: tty, apps : apps )
7375 }
7476
7577 /// Resolve terminal using TTY (when PID is not available)
7678 nonisolated func resolve( tty: String ) -> ResolvedTerminal ? {
7779 // Parse TTY to find a PID associated with it
7880 let tree = ProcessTreeBuilder . shared. buildTree ( )
81+ let apps = Self . getRunningApps ( )
7982
8083 // Find processes on this TTY
8184 for (pid, info) in tree {
8285 guard let processTTY = info. tty else { continue }
8386 if tty. hasSuffix ( processTTY) || processTTY == tty {
8487 // Found a process on this TTY, resolve from here
85- if let resolved = resolveFromTree ( pid: pid, tree: tree) {
88+ if let resolved = resolveFromTree ( pid: pid, tree: tree, apps : apps ) {
8689 return resolved
8790 }
8891 }
@@ -94,12 +97,12 @@ struct TerminalResolver: Sendable {
9497 // MARK: - Private
9598
9699 /// Walk up from a PID in a pre-built tree
97- private nonisolated func resolveFromTree( pid: Int , tree: [ Int : ProcessInfo ] ) -> ResolvedTerminal ? {
100+ private nonisolated func resolveFromTree( pid: Int , tree: [ Int : ProcessInfo ] , apps : [ NSRunningApplication ] ) -> ResolvedTerminal ? {
98101 let isInTmux = ProcessTreeBuilder . shared. isInTmux ( pid: pid, tree: tree)
99102 let tty = findTTY ( forPid: pid, tree: tree)
100103
101104 if let appInfo = TerminalAppRegistry . appInfo ( forPid: pid, tree: tree) ,
102- let bundleId = findRunningBundleId ( for: appInfo) {
105+ let bundleId = findRunningBundleId ( for: appInfo, apps : apps ) {
103106 return ResolvedTerminal (
104107 appInfo: appInfo,
105108 bundleId: bundleId,
@@ -112,7 +115,7 @@ struct TerminalResolver: Sendable {
112115 }
113116
114117 /// Try to find the terminal via tmux client PID
115- private nonisolated func resolveViaTmuxClient( claudePid: Int , tree: [ Int : ProcessInfo ] , tty: String ? ) -> ResolvedTerminal ? {
118+ private nonisolated func resolveViaTmuxClient( claudePid: Int , tree: [ Int : ProcessInfo ] , tty: String ? , apps : [ NSRunningApplication ] ) -> ResolvedTerminal ? {
116119 // Find tmux server in the parent chain
117120 var current = claudePid
118121 var depth = 0
@@ -139,7 +142,7 @@ struct TerminalResolver: Sendable {
139142
140143 // Walk up from this client to find the terminal
141144 if let appInfo = TerminalAppRegistry . appInfo ( forPid: pid, tree: tree) ,
142- let bundleId = findRunningBundleId ( for: appInfo) {
145+ let bundleId = findRunningBundleId ( for: appInfo, apps : apps ) {
143146 return ResolvedTerminal (
144147 appInfo: appInfo,
145148 bundleId: bundleId,
@@ -155,11 +158,10 @@ struct TerminalResolver: Sendable {
155158 /// Fallback: check all running apps for known terminals.
156159 /// Only returns a result if exactly one terminal app is running,
157160 /// otherwise we can't determine which terminal owns this session.
158- private nonisolated func resolveFromRunningApps( tty: String ? = nil ) -> ResolvedTerminal ? {
159- let runningApps = Self . getRunningApps ( )
161+ private nonisolated func resolveFromRunningApps( tty: String ? = nil , apps: [ NSRunningApplication ] ) -> ResolvedTerminal ? {
160162 var matches : [ ( appInfo: TerminalAppInfo , bundleId: String ) ] = [ ]
161163
162- for app in runningApps {
164+ for app in apps {
163165 guard let bundleId = app. bundleIdentifier,
164166 let appInfo = TerminalAppRegistry . appInfo ( forBundleId: bundleId) else {
165167 continue
@@ -181,10 +183,9 @@ struct TerminalResolver: Sendable {
181183 }
182184
183185 /// Find the bundle ID of a running instance for a terminal app
184- private nonisolated func findRunningBundleId( for appInfo: TerminalAppInfo ) -> String ? {
185- let runningApps = Self . getRunningApps ( )
186+ private nonisolated func findRunningBundleId( for appInfo: TerminalAppInfo , apps: [ NSRunningApplication ] ) -> String ? {
186187 for bundleId in appInfo. bundleIds {
187- if runningApps . contains ( where: { $0. bundleIdentifier == bundleId } ) {
188+ if apps . contains ( where: { $0. bundleIdentifier == bundleId } ) {
188189 return bundleId
189190 }
190191 }
0 commit comments