File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -247,14 +247,37 @@ export function registerDefaultTools(registry: ToolRegistry): void {
247247 }
248248 grepArgs . push ( pattern , path ) ;
249249
250+ const runGrep = async ( extraArgs : string [ ] = [ ] ) => {
251+ return execFileAsync ( "grep" , [ ...extraArgs , ...grepArgs ] , { timeout : 30000 } ) ;
252+ } ;
253+
250254 try {
251- const { stdout } = await execFileAsync ( "grep" , grepArgs , { timeout : 30000 } ) ;
255+ const { stdout } = await runGrep ( ) ;
252256 return stdout || "No matches found" ;
253257 } catch ( error : any ) {
254258 // grep exits with code 1 when no matches found — not an error
255259 if ( error . code === 1 ) {
256260 return "No matches found" ;
257261 }
262+
263+ const stderr = typeof error ?. stderr === "string" ? error . stderr : "" ;
264+ const isRegexSyntaxError = error . code === 2
265+ && / ( i n v a l i d r e g u l a r e x p r e s s i o n | i n v a l i d r e p e t i t i o n c o u n t | b r a c e s n o t b a l a n c e d | r e p e t i t i o n - o p e r a t o r o p e r a n d i n v a l i d | u n m a t c h e d ( \s * \\ ? \{ ) ? ) / i. test ( stderr ) ;
266+
267+ // BSD grep uses basic regex by default and can reject patterns that work in ERE.
268+ // Retry with -E so patterns like \$\{[A-Z_][A-Z0-9_]*:- are handled.
269+ if ( isRegexSyntaxError ) {
270+ try {
271+ const { stdout } = await runGrep ( [ "-E" ] ) ;
272+ return stdout || "No matches found" ;
273+ } catch ( extendedError : any ) {
274+ if ( extendedError . code === 1 ) {
275+ return "No matches found" ;
276+ }
277+ throw extendedError ;
278+ }
279+ }
280+
258281 throw error ;
259282 }
260283 } ) ;
Original file line number Diff line number Diff line change @@ -251,6 +251,28 @@ describe("Default Tools", () => {
251251 fs . unlinkSync ( tmpFile ) ;
252252 } ) ;
253253
254+ it ( "should retry grep with extended regex when BSD basic regex rejects pattern" , async ( ) => {
255+ const registry = new ToolRegistry ( ) ;
256+ registerDefaultTools ( registry ) ;
257+ const executor = new LocalExecutor ( registry ) ;
258+
259+ const fs = await import ( "fs" ) ;
260+ const os = await import ( "os" ) ;
261+ const path = await import ( "path" ) ;
262+ const tmpFile = path . join ( os . tmpdir ( ) , `test-grep-bsd-${ Date . now ( ) } .txt` ) ;
263+ fs . writeFileSync ( tmpFile , "export DEFAULT=${MY_VAR:-fallback}\n" , "utf-8" ) ;
264+
265+ const result = await executeWithChain ( [ executor ] , "grep" , {
266+ pattern : "\\$\\{[A-Z_][A-Z0-9_]*:-" ,
267+ path : tmpFile ,
268+ } ) ;
269+
270+ expect ( result . status ) . toBe ( "success" ) ;
271+ expect ( result . output ) . toContain ( "DEFAULT=${MY_VAR:-fallback}" ) ;
272+
273+ fs . unlinkSync ( tmpFile ) ;
274+ } ) ;
275+
254276 it ( "should prevent grep command injection via pattern" , async ( ) => {
255277 const registry = new ToolRegistry ( ) ;
256278 registerDefaultTools ( registry ) ;
You can’t perform that action at this time.
0 commit comments