neoclide/coc.nvim · error

Regex is too complex for the JavaScript search fallback; ins

Error message

Regex is too complex for the JavaScript search fallback; install ripgrep to use it safely

What it means

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.

Source

Thrown at src/mcp/tools/workspace.ts:118

}

export function escapeRegExp(text: string): string {
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

function unsafeFallbackRegex(pattern: string): boolean {
  // JavaScript RegExp has no execution timeout. Keep the no-ripgrep fallback
  // to expressions without the constructs most commonly responsible for
  // catastrophic backtracking; ripgrep remains the unrestricted regex path.
  return pattern.length > 1000
    || /\\[1-9]/.test(pattern)
    || /\(\?[=!<]/.test(pattern)
    || /\([^)]*[+*{][^)]*\)\s*(?:[+*?]|\{)/.test(pattern)
}

export async function searchWithJs(pattern: string, args: any, root: string, maxResults: number): Promise<SearchMatch[]> {
  if (args.regex === true && unsafeFallbackRegex(pattern)) {
    throw new Error('Regex is too complex for the JavaScript search fallback; install ripgrep to use it safely')
  }
  let include = new RelativePatternImpl(URI.file(root), typeof args.include === 'string' && args.include ? args.include : '**/*')
  let uris = await workspace.findFiles(include, args.exclude || null, 500)
  // One (first) match per line, searched from the start of every line: the
  // global flag would carry lastIndex across lines and skip matches.
  let flags = args.caseSensitive === true ? '' : 'i'
  let source = args.regex === true ? pattern : escapeRegExp(pattern)
  let re: RegExp
  try {
    re = new RegExp(source, flags)
  } catch (e) {
    throw new Error(`Invalid regex: ${e instanceof Error ? e.message : String(e)}`)
  }
  let results: SearchMatch[] = []
  for (let uri of uris) {
    if (results.length >= maxResults) break
    let filepath = uri.fsPath
    if (checkPath(filepath)) continue

View on GitHub (pinned to 50e974d969)

Solutions

  1. Install ripgrep so searches run through the native engine (`rg` on PATH)
  2. Rewrite the pattern without lookarounds or nested quantifiers (use character classes or restructure the expression)
  3. Run the search with `regex: false` (literal search) if the exact regex features aren't needed
  4. Split a complex pattern into multiple simpler searches

Example fix

// before
{ regex: true, query: "(?<=@)\w+" } // lookbehind, unsafe fallback
// after
{ regex: true, query: "@\w+" } // or install ripgrep
Defensive patterns

Strategy: fallback

Validate before calling

function unsafeFallbackRegex(p: string): boolean {
  return /\(\?[=!<]/.test(p) || /\([^)]*[+*{][^)]*\)\s*(?:[+*?]|\{)/.test(p)
}
if (args.regex === true && unsafeFallbackRegex(args.query) && !hasRipgrep()) {
  args.regex = false // literal search instead
}

Try / catch

try {
  matches = await searchWithJs(pattern, args, root, max)
} catch (e) {
  if (/too complex for the JavaScript search fallback/.test(e.message)) {
    matches = await searchWithJs(escapeRegExp(pattern), { ...args, regex: false }, root, max)
  } else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31). Data as JSON: /api/errors/ddaa968ac239c17d. Report an issue: GitHub.