From c4034e7caf2191d77dcac260446c5451a74b8849 Mon Sep 17 00:00:00 2001 From: T0mSIlver Date: Sat, 27 Jun 2026 00:40:42 +0000 Subject: [PATCH] fix(grep): honor head_limit values above 100 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The head_limit clamp read 'if head_limit < limit and head_limit > 0', so any value >= 100 was discarded and the cap stayed at 100 — the model could never raise the limit despite the schema saying it shows all results. Honor any positive head_limit, document that output is capped at the first 100 lines when head_limit is omitted, and add a regression test. --- src/fastcontext/agent/tool/grep.py | 7 +++---- tests/test_tool.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/fastcontext/agent/tool/grep.py b/src/fastcontext/agent/tool/grep.py index 4ee161f..abfa330 100644 --- a/src/fastcontext/agent/tool/grep.py +++ b/src/fastcontext/agent/tool/grep.py @@ -55,7 +55,7 @@ class GrepTool(Tool): "head_limit": { "type": "number", "minimum": 0, - "description": 'Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). When unspecified, shows all results from ripgrep.', + "description": 'Limit output to first N lines/entries, equivalent to "| head -N". Works across all output modes: content (limits output lines), files_with_matches (limits file paths), count (limits count entries). When unspecified, results are capped at the first 100 lines.', }, "multiline": { "type": "boolean", @@ -104,9 +104,8 @@ async def call(self, parameters: str, **kwargs) -> str: return "No matches found" limit = 100 - if head_limit is not None: - if head_limit < limit and head_limit > 0: - limit = head_limit + if head_limit is not None and head_limit > 0: + limit = head_limit lines = output.splitlines() if len(lines) > limit: diff --git a/tests/test_tool.py b/tests/test_tool.py index aa1c311..adc2223 100644 --- a/tests/test_tool.py +++ b/tests/test_tool.py @@ -1,11 +1,37 @@ import asyncio import json +import tempfile +from pathlib import Path from fastcontext.agent.tool.glob import GlobTool from fastcontext.agent.tool.grep import GrepTool from fastcontext.agent.tool.read import ReadTool +def test_grep_head_limit(): + """head_limit must be honored for values above the default 100-line cap, + and fall back to 100 when unspecified.""" + grep = GrepTool() + with tempfile.TemporaryDirectory() as cwd: + # 250 matching lines, single file, so output exceeds any tested limit. + (Path(cwd) / "haystack.txt").write_text("\n".join("MATCH" for _ in range(250)), encoding="utf-8") + + def lines_for(params): + out = asyncio.run(grep.call(json.dumps(params), cwd=cwd)) + return out.splitlines() + + # Above the default cap: previously clamped to 100, must now be honored. + # Truncated output = first `limit` lines + one "Results truncated" note. + out = lines_for({"pattern": "MATCH", "output_mode": "content", "head_limit": 150}) + assert len(out) == 151, f"expected 150 lines + note, got {len(out)}" + assert "truncated to first 150" in out[-1] + + # Unspecified: 100-line default cap still applies. + out = lines_for({"pattern": "MATCH", "output_mode": "content"}) + assert len(out) == 101, f"expected 100 lines + note, got {len(out)}" + assert "truncated to first 100" in out[-1] + + def test_grep_tool(): grep = GrepTool() params = {