From b8a0882d299bcc56734ebc2df4f50d69174fc843 Mon Sep 17 00:00:00 2001 From: weiconghe <46336277+weiconghe@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:53:22 +0800 Subject: [PATCH] fix(file_system): handle PermissionError on scandir in gitignore walker (#1634) Wrap os.scandir() with PermissionError guard in _iter_gitignore_files' scan() function to prevent crash when a subdirectory is not readable. Previously, PermissionError on the scandir call itself crashed the entire gitignore walker, even when the path was listed in ignored_paths. Fixes oraios/serena#1624 Co-authored-by: Claude --- CHANGELOG.md | 1 + src/serena/util/file_system.py | 7 ++++- test/serena/util/test_file_system.py | 40 +++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fcd8b5f..387e9146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Status of the `main` branch. Changes prior to the next official version change w (which does not correctly unpack structured output) #1042 - Fix: Project-specific filtering of files for source files ignored the language backend. The check is really only possible for LSP. + - Fix: File system permission errors during gitignore scanning were not caught #1624 * CLI: - Fix `--project-from-cwd` hijacking git worktrees nested under a Serena project. `find_project_root` diff --git a/src/serena/util/file_system.py b/src/serena/util/file_system.py index 6ec1582e..06cedc8c 100644 --- a/src/serena/util/file_system.py +++ b/src/serena/util/file_system.py @@ -164,7 +164,12 @@ class GitignoreParser: queue: list[str] = [self.repo_root] def scan(abs_path: str | None) -> Iterator[str]: - for entry in os.scandir(abs_path): + try: + entries = os.scandir(abs_path) + except PermissionError as ex: + log.debug(f"Skipping unreadable directory {abs_path}: {ex}") + return + for entry in entries: try: if entry.is_dir(follow_symlinks=follow_symlinks): queue.append(entry.path) diff --git a/test/serena/util/test_file_system.py b/test/serena/util/test_file_system.py index 95ce9e61..ca8a0872 100644 --- a/test/serena/util/test_file_system.py +++ b/test/serena/util/test_file_system.py @@ -3,7 +3,6 @@ import shutil import tempfile from pathlib import Path -# Assuming the gitignore parser code is in a module named 'gitignore_parser' from pathspec import PathSpec from serena.util.file_system import GitignoreParser, GitignoreSpec, match_path @@ -715,3 +714,42 @@ src/*.o # foo.txt in other/ should NOT be ignored (outside foo/ subtree) assert not parser.should_ignore("other/foo.txt"), "other/foo.txt should NOT be ignored by foo/.gitignore" + + +class TestGitignoreParserPermissionError: + """Test PermissionError handling in GitignoreParser.""" + + def setup_method(self): + self.test_dir = tempfile.mkdtemp() + self.repo_path = Path(self.test_dir) + + def teardown_method(self): + shutil.rmtree(self.test_dir, ignore_errors=True) + + def test_scandir_permission_error_on_subdirectory(self): + """ + Test that GitignoreParser does not crash when a subdirectory + is not readable (PermissionError on os.scandir). + + Regression test for https://github.com/oraios/serena/issues/1624 + """ + # Create a root .gitignore + gitignore = self.repo_path / ".gitignore" + gitignore.write_text("*.log\n") + + # Create an unreadable subdirectory + unreadable = self.repo_path / "unreadable_dir" + unreadable.mkdir() + + # Remove read permissions (Unix only; on Windows this is a no-op) + old_mode = os.stat(unreadable).st_mode + os.chmod(unreadable, 0o000) + + try: + # This should not raise PermissionError + parser = GitignoreParser(str(self.repo_path)) + # Parser should still function + assert parser.should_ignore("test.log") + finally: + # Restore permissions so teardown can clean up + os.chmod(unreadable, old_mode)