diff --git a/openhands-tools/openhands/tools/glob/impl.py b/openhands-tools/openhands/tools/glob/impl.py index ddf7ae941e..3f8864e520 100644 --- a/openhands-tools/openhands/tools/glob/impl.py +++ b/openhands-tools/openhands/tools/glob/impl.py @@ -168,17 +168,14 @@ def _execute_with_ripgrep( env=sanitized_env(), ) - # Parse output into file paths - file_paths = [] + # Parse output into file paths (ripgrep already sorted by --sortr=modified) + lines = [] if result.stdout: - for line in result.stdout.strip().split("\n"): - if line: - file_paths.append(str(Path(line).resolve())) - # Limit to first 100 files - if len(file_paths) >= 100: - break - - truncated = len(file_paths) >= 100 + lines = [line for line in result.stdout.strip().split("\n") if line] + + # Limit to first 100 files; truncated only if matches were actually dropped + truncated = len(lines) > 100 + file_paths = [str(Path(line).resolve()) for line in lines[:100]] return file_paths, truncated diff --git a/tests/tools/glob/test_glob_executor.py b/tests/tools/glob/test_glob_executor.py index ce8cf3e727..1081997a13 100644 --- a/tests/tools/glob/test_glob_executor.py +++ b/tests/tools/glob/test_glob_executor.py @@ -171,6 +171,36 @@ def test_glob_executor_truncation(): assert observation.truncated is True +def test_glob_executor_exactly_100_files_not_truncated(): + """Exactly 100 matches must not be reported as truncated. + + Regression test: the ripgrep backend used to stop collecting at 100 and + then flag ``truncated = len >= 100``, so a result set of exactly 100 files + was reported as truncated even though nothing was dropped. The Python + fallback backend already used a strict ``> 100`` comparison. + """ + with tempfile.TemporaryDirectory() as temp_dir: + for i in range(100): + (Path(temp_dir) / f"file_{i:03d}.txt").write_text(f"Content {i}") + + # Default backend (ripgrep when available) + executor = GlobExecutor(working_dir=temp_dir) + observation = executor(GlobAction(pattern="*.txt")) + + assert observation.is_error is False + assert len(observation.files) == 100 + assert observation.truncated is False + + # Forced Python fallback backend must agree + fallback_executor = GlobExecutor(working_dir=temp_dir) + fallback_executor._ripgrep_available = False + fallback_observation = fallback_executor(GlobAction(pattern="*.txt")) + + assert fallback_observation.is_error is False + assert len(fallback_observation.files) == 100 + assert fallback_observation.truncated is False + + def test_glob_executor_complex_patterns(): """Test complex glob patterns.""" with tempfile.TemporaryDirectory() as temp_dir: