33 */
44
55import { describe , it , expect , jest , beforeEach } from '@jest/globals' ;
6- import { execSync } from 'child_process' ;
6+ import { execFileSync } from 'child_process' ;
77import {
88 listAgentProcesses ,
99 batchGetProcessCwds ,
@@ -12,18 +12,18 @@ import {
1212} from '../../utils/process' ;
1313
1414jest . mock ( 'child_process' , ( ) => ( {
15- execSync : jest . fn ( ) ,
15+ execFileSync : jest . fn ( ) ,
1616} ) ) ;
1717
18- const mockedExecSync = execSync as jest . MockedFunction < typeof execSync > ;
18+ const mockedExecFileSync = execFileSync as jest . MockedFunction < typeof execFileSync > ;
1919
2020describe ( 'listAgentProcesses' , ( ) => {
2121 beforeEach ( ( ) => {
22- mockedExecSync . mockReset ( ) ;
22+ mockedExecFileSync . mockReset ( ) ;
2323 } ) ;
2424
2525 it ( 'should parse ps aux | grep output and post-filter by executable name' , ( ) => {
26- mockedExecSync . mockReturnValue (
26+ mockedExecFileSync . mockReturnValue (
2727 'user 78070 1.0 0.5 485636016 245952 s018 S+ 11:18PM 1:55.14 claude\n' +
2828 'user 55106 0.1 0.4 485620368 72496 s015 S+ 9Mar26 8:06.36 claude\n' ,
2929 ) ;
@@ -38,7 +38,7 @@ describe('listAgentProcesses', () => {
3838 } ) ;
3939
4040 it ( 'should filter out non-matching executables' , ( ) => {
41- mockedExecSync . mockReturnValue (
41+ mockedExecFileSync . mockReturnValue (
4242 'user 100 0.0 0.0 0 0 s001 S 1:00PM 0:00 claude\n' +
4343 'user 200 0.0 0.0 0 0 s002 S 1:00PM 0:00 claude-helper --pid 100\n' +
4444 'user 300 0.0 0.0 0 0 s003 S 1:00PM 0:00 /usr/bin/claude\n' ,
@@ -50,46 +50,46 @@ describe('listAgentProcesses', () => {
5050 } ) ;
5151
5252 it ( 'should return empty array on command failure' , ( ) => {
53- mockedExecSync . mockImplementation ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
53+ mockedExecFileSync . mockImplementation ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
5454 expect ( listAgentProcesses ( 'claude' ) ) . toEqual ( [ ] ) ;
5555 } ) ;
5656
5757 it ( 'should handle empty output' , ( ) => {
58- mockedExecSync . mockReturnValue ( '' ) ;
58+ mockedExecFileSync . mockReturnValue ( '' ) ;
5959 expect ( listAgentProcesses ( 'claude' ) ) . toEqual ( [ ] ) ;
6060 } ) ;
6161
6262 it ( 'should reject empty pattern' , ( ) => {
6363 expect ( listAgentProcesses ( '' ) ) . toEqual ( [ ] ) ;
64- expect ( mockedExecSync ) . not . toHaveBeenCalled ( ) ;
64+ expect ( mockedExecFileSync ) . not . toHaveBeenCalled ( ) ;
6565 } ) ;
6666
6767 it ( 'should reject patterns with shell injection characters' , ( ) => {
6868 expect ( listAgentProcesses ( 'claude; rm -rf /' ) ) . toEqual ( [ ] ) ;
6969 expect ( listAgentProcesses ( "claude' || true" ) ) . toEqual ( [ ] ) ;
7070 expect ( listAgentProcesses ( '$(whoami)' ) ) . toEqual ( [ ] ) ;
71- expect ( mockedExecSync ) . not . toHaveBeenCalled ( ) ;
71+ expect ( mockedExecFileSync ) . not . toHaveBeenCalled ( ) ;
7272 } ) ;
7373
7474 it ( 'should accept valid patterns with dashes and underscores' , ( ) => {
75- mockedExecSync . mockReturnValue ( '' ) ;
75+ mockedExecFileSync . mockReturnValue ( '' ) ;
7676 listAgentProcesses ( 'claude-code' ) ;
77- expect ( mockedExecSync ) . toHaveBeenCalled ( ) ;
77+ expect ( mockedExecFileSync ) . toHaveBeenCalled ( ) ;
7878
79- mockedExecSync . mockReset ( ) ;
80- mockedExecSync . mockReturnValue ( '' ) ;
79+ mockedExecFileSync . mockReset ( ) ;
80+ mockedExecFileSync . mockReturnValue ( '' ) ;
8181 listAgentProcesses ( 'my_agent' ) ;
82- expect ( mockedExecSync ) . toHaveBeenCalled ( ) ;
82+ expect ( mockedExecFileSync ) . toHaveBeenCalled ( ) ;
8383 } ) ;
8484} ) ;
8585
8686describe ( 'batchGetProcessCwds' , ( ) => {
8787 beforeEach ( ( ) => {
88- mockedExecSync . mockReset ( ) ;
88+ mockedExecFileSync . mockReset ( ) ;
8989 } ) ;
9090
9191 it ( 'should parse batched lsof output' , ( ) => {
92- mockedExecSync . mockReturnValue (
92+ mockedExecFileSync . mockReturnValue (
9393 'p78070\nn/Users/user/ai-devkit\np55106\nn/Users/user/other-project\n' ,
9494 ) ;
9595
@@ -104,7 +104,7 @@ describe('batchGetProcessCwds', () => {
104104
105105 it ( 'should return partial results when lsof succeeds for some PIDs' , ( ) => {
106106 // lsof might not return entries for dead processes
107- mockedExecSync . mockReturnValue (
107+ mockedExecFileSync . mockReturnValue (
108108 'p78070\nn/Users/user/ai-devkit\n' ,
109109 ) ;
110110
@@ -114,7 +114,7 @@ describe('batchGetProcessCwds', () => {
114114 } ) ;
115115
116116 it ( 'should return empty map on total failure' , ( ) => {
117- mockedExecSync . mockImplementation ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
117+ mockedExecFileSync . mockImplementation ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
118118 const cwds = batchGetProcessCwds ( [ 78070 ] ) ;
119119 // Falls through to pwdx fallback which also fails
120120 expect ( cwds . size ) . toBe ( 0 ) ;
@@ -123,11 +123,11 @@ describe('batchGetProcessCwds', () => {
123123
124124describe ( 'batchGetProcessStartTimes' , ( ) => {
125125 beforeEach ( ( ) => {
126- mockedExecSync . mockReset ( ) ;
126+ mockedExecFileSync . mockReset ( ) ;
127127 } ) ;
128128
129129 it ( 'should parse ps lstart output' , ( ) => {
130- mockedExecSync . mockReturnValue (
130+ mockedExecFileSync . mockReturnValue (
131131 ' 78070 Wed Mar 18 23:18:01 2026\n' +
132132 ' 55106 Mon Mar 9 21:41:42 2026\n' ,
133133 ) ;
@@ -143,7 +143,7 @@ describe('batchGetProcessStartTimes', () => {
143143 } ) ;
144144
145145 it ( 'should skip lines with unparseable dates' , ( ) => {
146- mockedExecSync . mockReturnValue (
146+ mockedExecFileSync . mockReturnValue (
147147 ' 78070 Wed Mar 18 23:18:01 2026\n' +
148148 ' 99999 INVALID_DATE\n' ,
149149 ) ;
@@ -154,20 +154,20 @@ describe('batchGetProcessStartTimes', () => {
154154 } ) ;
155155
156156 it ( 'should return empty map on failure' , ( ) => {
157- mockedExecSync . mockImplementation ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
157+ mockedExecFileSync . mockImplementation ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
158158 expect ( batchGetProcessStartTimes ( [ 78070 ] ) ) . toEqual ( new Map ( ) ) ;
159159 } ) ;
160160} ) ;
161161
162162describe ( 'enrichProcesses' , ( ) => {
163163 beforeEach ( ( ) => {
164- mockedExecSync . mockReset ( ) ;
164+ mockedExecFileSync . mockReset ( ) ;
165165 } ) ;
166166
167167 it ( 'should populate cwd and startTime on processes' , ( ) => {
168168 // First call: batchGetProcessCwds (lsof)
169169 // Second call: batchGetProcessStartTimes (ps lstart)
170- mockedExecSync
170+ mockedExecFileSync
171171 . mockReturnValueOnce ( 'p100\nn/projects/app\n' )
172172 . mockReturnValueOnce ( ' 100 Wed Mar 18 23:18:01 2026\n' ) ;
173173
@@ -182,12 +182,12 @@ describe('enrichProcesses', () => {
182182
183183 it ( 'should return empty array for empty input' , ( ) => {
184184 expect ( enrichProcesses ( [ ] ) ) . toEqual ( [ ] ) ;
185- expect ( mockedExecSync ) . not . toHaveBeenCalled ( ) ;
185+ expect ( mockedExecFileSync ) . not . toHaveBeenCalled ( ) ;
186186 } ) ;
187187
188188 it ( 'should handle partial failures' , ( ) => {
189189 // lsof succeeds, ps lstart fails
190- mockedExecSync
190+ mockedExecFileSync
191191 . mockReturnValueOnce ( 'p100\nn/projects/app\n' )
192192 . mockImplementationOnce ( ( ) => { throw new Error ( 'fail' ) ; } ) ;
193193
0 commit comments