sauravsolanki
### Submission checklist - [x] This is a bug, not a usage question. - [x] I added a clear and descriptive title that summarizes this issue. - [x] I used the GitHub search to find a similar question and didn't find it. - [x] I am sure that this is a bug in LangChain rather than my code. - [x] The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package). - [x] This is not related to the langchain-community package. - [x] I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS. ### Package (Required) - [x] langchain - [ ] langchain-openai - [ ] langchain-anthropic - [ ] langchain-classic - [ ] langchain-core - [ ] langchain-model-profiles - [ ] langchain-tests - [ ] langchain-text-splitters - [ ] langchain-chroma - [ ] langchain-deepseek - [ ] langchain-exa - [ ] langchain-fireworks - [ ] langchain-groq - [ ] langchain-huggingface - [ ] langchain-mistralai - [ ] langchain-nomic - [ ] langchain-ollama - [ ] langchain-openrouter - [ ] langchain-perplexity - [ ] langchain-qdrant - [ ] langchain-typesafe - [ ] langchain-xai - [ ] Other / not sure / general ### Related Issues / PRs #40255 (fallback ReDoS) touches the same fallback path but is a different problem. ### Reproduction Steps / Example Code (Python) ```python import subprocess import tempfile from pathlib import Path from langchain.agents.middleware.file_search import FilesystemFileSearchMiddleware d = tempfile.mkdtemp() Path(d, "a.txt").write_text("foobar\nfoobaz\n") # 1. A regex Python accepts but ripgrep rejects (look-ahead) for use_rg in (True, False): mw = FilesystemFileSearchMiddleware(root_path=d, use_ripgrep=use_rg) out = mw.grep_search.invoke({"pattern": "foo(?=bar)", "output_mode": "content"}) print(f"use_ripgrep={use_rg}: {out!r}") r = subprocess.run(["rg", "--json", "--", "foo(?=bar)", d], capture_output=True, text=True) print("rg returncode:", r.returncode, "| stderr:", r.stderr.strip().splitlines()[-1]) # 2. max_file_size_mb is only enforced by the Python fallback Path(d, "big.txt").write_text("needle\n" + "x" * (11 * 1024 * 1024)) for use_rg in (True, False): mw = FilesystemFileSearchMiddleware(root_path=d, use_ripgrep=use_rg, max_file_size_mb=10) print(f"max_file_size_mb=10 use_ripgrep={use_rg}:", mw.grep_search.invoke({"pattern": "needle"})) ``` ### Error Message and Stack Trace (if applicable) ```shell No exception. Output of the script above (ripgrep 13.0.0 on PATH): use_ripgrep=True: 'No matches found' use_ripgrep=False: '/a.txt:1:foobar' rg returncode: 2 | stderr: error: look-around, including look-ahead and look-behind, is not supported max_file_size_mb=10 use_ripgrep=True: /big.txt max_file_size_mb=10 use_ripgrep=False: No matches found Expected: identical results regardless of `use_ripgrep`. Either the tool falls back to the Python search when ripgrep rejects the pattern (so `/a.txt:1:foobar` is returned), or it surfaces ripgrep's error to the model instead of claiming there are no matches. And `max_file_size_mb` should be honoured on both paths. ``` ### Description `FilesystemFileSearchMiddleware.grep_search` (`langchain/agents/middleware/file_search.py`) is documented as using ripgrep when available and falling back to a pure-Python search otherwise. The fallback never runs on a ripgrep *error*, only when the binary is missing or times out: ```python # grep_search results = None if self.use_ripgrep: with suppress(FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired): results = self._ripgrep_search(pattern, path, include) if results is None: results = self._python_search(pattern, path, include) if not results: return "No matches found" # _ripgrep_search result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, check=False) ... for line in result.stdout.splitlines(): # stdout is empty on error ... return results # {} , not None ``` `check=False` means a non-zero exit never raises `CalledProcessError`, so `_ripgrep_search` parses an empty stdout and returns `{}`. That is not `None`, so the fallback is skipped and the model is told there are no matches. ripgrep exits with code 2 for any pattern its regex engine rejects: look-ahead/look-behind, backreferences, some Unicode property escapes, etc. These are all valid Python regexes, so a model that writes `(?<=foo)bar` gets a confident "No matches found" for a file that does match, and nothing in the result hints that the search failed. `result.stderr`, which contains the exact reason, is discarded. A second divergence in the same function: `max_file_size_mb` is checked in `_python_search` (`file_path.stat().st_size > self.max_file_size_bytes`) but the ripgrep command is built without `--max-filesize`, so the two paths return different results for the same inputs (see the 11 MB file in the repro). **Proposed fix** In `_ripgrep_search`, after `subprocess.run`: ```python if result.returncode not in (0, 1): # 0 = matches, 1 = no matches, 2 = error return None # let grep_search fall back to the Python search ``` (or, alternatively, return a message built from `result.stderr` so the model sees "Invalid regex pattern for ripgrep: look-around ... is not supported" and can rewrite the pattern), and add `"--max-filesize", str(self.max_file_size_bytes)` to `cmd` so the size limit applies on both paths. Both are a few lines plus tests; happy to open the PR if assigned. ### System Info Reproduced on langchain master (57236d5) and on the released packages below. ripgrep 13.0.0. System Information ------------------ > OS: Linux > OS Version: #204-Ubuntu SMP Wed Sep 2 13:09:23 UTC 2026 > Python Version: 3.13.11 (main, Jan 14 2026, 19:38:04) [Clang 21.1.4 ] Package Information ------------------- > langchain_core: 1.6.3 > langchain: 1.4.0 > langsmith: 0.8.18 > langgraph: 1.2.11 > pydantic: 2.12.5 ### Social handles (optional) LinkedIn: https://linkedin.com/in/sauravsolanki GitHub: https://github.com/sauravsolanki