Skip to content

Bug: pattern_search silently drops all matches from CRLF files on Windows #1

Description

@intfoo

Description

On Windows, pattern_search (and any tool that calls runRg with line-by-line parsing) silently drops every match from files that use CRLF line endings (\r\n).

The root cause is in execPatternSearch (server.ts:1293):

const m = line.match(/^(.+?):(\d+):(.*)$/);

When ripgrep outputs matches from a CRLF file, it preserves the \r\n endings. After stdout.split("\n"), the \n is consumed but \r remains at the end of each line. Since . does not match \r and $ does not anchor before \r, the entire regex fails to match, and the line is silently skipped.

Minimal reproduction:

// CRLF line → match fails
"abc:1:hello\r".match(/^(.+?):(\d+):(.*)$/);  // null

// LF line → match succeeds
"abc:1:hello".match(/^(.+?):(\d+):(.*)$/);     // ["abc:1:hello", "abc", "1", "hello"]

Impact: Any file committed with CRLF endings (common on Windows, especially files edited in certain IDEs) is completely invisible to pattern_search. The bug is silent — no error, no warning, just missing results.


Affected Code

src/mcp/server.ts line 1293 and any other runRg consumer that parses output line-by-line with the same regex pattern.


Proposed Fix

Option A — Make the regex CRLF-tolerant:

- const m = line.match(/^(.+?):(\d+):(.*)$/);
+ const m = line.match(/^(.+?):(\d+):(.*)\r?$/);

Option B — Strip \r before parsing:

  for (const line of stdout.split("\n")) {
+   const clean = line.replace(/\r$/, "");
-   const m = line.match(/^(.+?):(\d+):(.*)$/);
+   const m = clean.match(/^(.+?):(\d+):(.*)$/);

Option B is more defensive and benefits any future regex changes that might forget \r?.


Reproduction Steps

  1. Create a CRLF test file on Windows:

    mkdir -p /tmp/test-project
    printf "export class Hello {\r\n  world() {}\r\n}\r\n" > /tmp/test-project/hello.js
  2. Create an LF test file in the same directory:

    printf "export class Hello {\n  world() {}\n}\n" > /tmp/test-project/hello-lf.js
  3. Run lexis MCP against this project and call pattern_search:

    {
      "tool": "pattern_search",
      "arguments": { "pattern": "Hello" }
    }
  4. Expected: Both hello.js and hello-lf.js appear in results.

  5. Actual: Only hello-lf.js appears. hello.js is silently missing.


Environment

  • OS: Windows 10/11 (CRLF is the default line ending)
  • Node.js: v22.x
  • ripgrep: 15.0.0 (bundled via @vscode/ripgrep)
  • lexis-mcp: latest

Additional Notes

  • The same regex pattern may also affect other runRg consumers in server.ts. A grep for (.+?):(\d+):(.*) would reveal all occurrences that need patching.
  • search_code also calls runRg internally and likely has the same issue if it parses stdout line-by-line.
  • This bug is particularly insidious because it is silent — no error, no warning, just missing results. On a mixed-line-ending project (common in teams with both Windows and macOS/Linux devs), some files will appear in search results and others won't, with no obvious pattern to the user.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions