{"record":{"id":"4693767678c555cd","repo":"can1357/oh-my-pi","slug":"invalid-regex-message-replace-invalid-regular","errorCode":null,"errorMessage":"Invalid regex: ${message.replace(/^Invalid regular expression:\\s*/i, \"\")}","messagePattern":"Invalid regex: (.+?)","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/grep.ts","lineNumber":438,"sourceCode":" * large for native grep (>`NATIVE_GREP_MAX_FILE_BYTES`, which native grep silently\n * skips). Mirrors the native probe's output (sorted, deduped indexes) so\n * `buildVirtualMatches` rebuilds context/ranges identically; only the regex dialect\n * differs for these oversized inputs (the pre-RE2-parity behavior).\n */\nfunction jsMatchedLineIndexes(\n\tcontent: string,\n\tlines: readonly string[],\n\tpattern: string,\n\tignoreCase: boolean,\n\tmultiline: boolean,\n): number[] {\n\tconst flags = `${ignoreCase ? \"i\" : \"\"}${multiline ? \"gm\" : \"\"}`;\n\tlet regex: RegExp;\n\ttry {\n\t\tregex = new RegExp(pattern, flags);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new ToolError(`Invalid regex: ${message.replace(/^Invalid regular expression:\\s*/i, \"\")}`);\n\t}\n\tif (!multiline) {\n\t\tconst out: number[] = [];\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tregex.lastIndex = 0;\n\t\t\tif (regex.test(lines[i] ?? \"\")) out.push(i);\n\t\t}\n\t\treturn out;\n\t}\n\tconst { starts } = indexSearchLines(content);\n\tconst seen = new Set<number>();\n\tconst out: number[] = [];\n\tlet match = regex.exec(content);\n\twhile (match !== null) {\n\t\tconst lineIndex = findLineIndex(starts, match.index);\n\t\tif (lineIndex >= 0 && !seen.has(lineIndex)) {\n\t\t\tseen.add(lineIndex);\n\t\t\tout.push(lineIndex);","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/grep.ts#L420-L456","documentation":"The JS fallback matcher (jsMatchedLineIndexes) compiles the user pattern with new RegExp; an invalid pattern throws a SyntaxError which is converted to a clean ToolError 'Invalid regex: <detail>' with the engine's boilerplate prefix stripped.","triggerScenarios":"Patterns like 'foo(' , '[unclosed', 'a{2,1}', a stray '*' after nothing to repeat, or invalid backreferences passed as the grep pattern.","commonSituations":"Unescaped user text used as a regex (parentheses, brackets in prose or code snippets); patterns built by string concatenation; switching between tools where one accepts literal text and this one expects regex.","solutions":["Fix the regex syntax (close groups/brackets, valid quantifier bounds)","Escape metacharacters in literal text: \\( \\[ \\. etc.","Pre-test the pattern with new RegExp(pattern, 'i') in a try/catch before calling"],"exampleFix":"// before\nconst pattern = `log(${level}`; // invalid\nawait grepTool.execute({ pattern, path })\n// after\nconst pattern = `log\\(${level}`; // escaped\nawait grepTool.execute({ pattern, path })","handlingStrategy":"validation","validationCode":"function assertValidRegex(pattern: string, flags = \"i\"): void {\n  try { new RegExp(pattern, flags); } catch (err) {\n    throw new Error(`Pattern is not a valid JS RegExp: ${pattern}`, { cause: err });\n  }\n}","typeGuard":"function isValidRegex(pattern: string): boolean {\n  try { new RegExp(pattern); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  return await grepTool.execute({ pattern, path });\n} catch (err) {\n  if (err instanceof ToolError && err.message.startsWith(\"Invalid regex:\")) {\n    const literal = pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n    return await grepTool.execute({ pattern: literal, path }); // retry as escaped literal\n  }\n  throw err;\n}","preventionTips":["Escape metacharacters when searching literal text","Pre-compile with new RegExp(pattern) in a try/catch before calling the tool","Avoid PCRE/RE2-only syntax; target JS RegExp semantics"],"tags":["regex","parsing","grep-tool"],"backgroundTag":"invalid-regex-pattern","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}