{"record":{"id":"ddaa968ac239c17d","repo":"neoclide/coc.nvim","slug":"regex-is-too-complex-for-the-javascript-search-fal","errorCode":null,"errorMessage":"Regex is too complex for the JavaScript search fallback; install ripgrep to use it safely","messagePattern":"Regex is too complex for the JavaScript search fallback; install ripgrep to use it safely","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/mcp/tools/workspace.ts","lineNumber":118,"sourceCode":"}\n\nexport function escapeRegExp(text: string): string {\n  return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction unsafeFallbackRegex(pattern: string): boolean {\n  // JavaScript RegExp has no execution timeout. Keep the no-ripgrep fallback\n  // to expressions without the constructs most commonly responsible for\n  // catastrophic backtracking; ripgrep remains the unrestricted regex path.\n  return pattern.length > 1000\n    || /\\\\[1-9]/.test(pattern)\n    || /\\(\\?[=!<]/.test(pattern)\n    || /\\([^)]*[+*{][^)]*\\)\\s*(?:[+*?]|\\{)/.test(pattern)\n}\n\nexport async function searchWithJs(pattern: string, args: any, root: string, maxResults: number): Promise<SearchMatch[]> {\n  if (args.regex === true && unsafeFallbackRegex(pattern)) {\n    throw new Error('Regex is too complex for the JavaScript search fallback; install ripgrep to use it safely')\n  }\n  let include = new RelativePatternImpl(URI.file(root), typeof args.include === 'string' && args.include ? args.include : '**/*')\n  let uris = await workspace.findFiles(include, args.exclude || null, 500)\n  // One (first) match per line, searched from the start of every line: the\n  // global flag would carry lastIndex across lines and skip matches.\n  let flags = args.caseSensitive === true ? '' : 'i'\n  let source = args.regex === true ? pattern : escapeRegExp(pattern)\n  let re: RegExp\n  try {\n    re = new RegExp(source, flags)\n  } catch (e) {\n    throw new Error(`Invalid regex: ${e instanceof Error ? e.message : String(e)}`)\n  }\n  let results: SearchMatch[] = []\n  for (let uri of uris) {\n    if (results.length >= maxResults) break\n    let filepath = uri.fsPath\n    if (checkPath(filepath)) continue","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/neoclide/coc.nvim/blob/50e974d9692461a69147d5cab146a8d3e439abe4/src/mcp/tools/workspace.ts#L100-L136","documentation":"When ripgrep is not used, workspace search falls back to a pure-JavaScript matcher. Some regex constructs (lookarounds `(?=`, `(?!`, `(?<`, nested quantifiers like `(a+)+`) risk catastrophic backtracking in JS, so `searchWithJs` refuses them and asks the user to install ripgrep instead.","triggerScenarios":"Calling the workspace search tool with `regex: true` and a pattern containing lookarounds or nested quantifiers (detected by `unsafeFallbackRegex`) while ripgrep is not available on the system.","commonSituations":"Environment without ripgrep installed (not on PATH); patterns ported from ripgrep/PCRE habits that use lookahead; a user-supplied search query that happens to include nested repetition.","solutions":["Install ripgrep so searches run through the native engine (`rg` on PATH)","Rewrite the pattern without lookarounds or nested quantifiers (use character classes or restructure the expression)","Run the search with `regex: false` (literal search) if the exact regex features aren't needed","Split a complex pattern into multiple simpler searches"],"exampleFix":"// before\n{ regex: true, query: \"(?<=@)\\w+\" } // lookbehind, unsafe fallback\n// after\n{ regex: true, query: \"@\\w+\" } // or install ripgrep","handlingStrategy":"fallback","validationCode":"function unsafeFallbackRegex(p: string): boolean {\n  return /\\(\\?[=!<]/.test(p) || /\\([^)]*[+*{][^)]*\\)\\s*(?:[+*?]|\\{)/.test(p)\n}\nif (args.regex === true && unsafeFallbackRegex(args.query) && !hasRipgrep()) {\n  args.regex = false // literal search instead\n}","typeGuard":null,"tryCatchPattern":"try {\n  matches = await searchWithJs(pattern, args, root, max)\n} catch (e) {\n  if (/too complex for the JavaScript search fallback/.test(e.message)) {\n    matches = await searchWithJs(escapeRegExp(pattern), { ...args, regex: false }, root, max)\n  } else throw e\n}","preventionTips":["Install ripgrep so the native engine handles all regexes","Prefer patterns without lookarounds/nested quantifiers when ripgrep may be absent","Fall back to literal (regex:false) search on this error","Test user-supplied regexes against unsafeFallbackRegex before invoking search"],"tags":["regex","ripgrep","fallback-limitation"],"backgroundTag":"catastrophic-backtracking-regex","analyzedSha":"50e974d9692461a69147d5cab146a8d3e439abe4","analyzedAt":"2026-08-31T11:17:23.966Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}