Fix dangling registered project poisoning project lookup/activation

A registered project whose root directory was deleted (e.g. a removed git
worktree) would raise FileNotFoundError from RegisteredProject.matches_root_path
(via Path.samefile). Because get_registered_project iterates matches_root_path
over every registered project, one dangling entry aborted activation of all
other, valid projects until an MCP server restart.

matches_root_path now treats a non-existent directory as a non-match instead of
letting the filesystem error propagate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Andrew NewellandClaude Opus 4.8 authored and Dominik Jain committed 2026-06-24 01:50:13 +02:00
1 parent bd322fcb5e
commit 4160732c7d
3 files changed
+52 -2

No files matched your search

+2
View File
@@ -20,6 +20,8 @@ Status of the `main` branch. Changes prior to the next official version change w
- Improve quoting/escaping of arguments in shell executions on Windows (via `oslex` dependency)
- Add tool parameter alias support, adding `name_path` as an alias for `name_path_pattern` in `find_symbol` tools
- Make tool call errors surface explicitly as errors at the MCP protocol level
- Fix: a registered project whose root directory was deleted while Serena was already running could break
`activate_project`/project lookup, raising `FileNotFoundError` in `RegisteredProject.matches_root_path`
* Language Servers:
- `typescript_vts`: Add `initialization_options` setting in `ls_specific_settings.typescript_vts`.
+7 -2
View File
@@ -705,9 +705,14 @@ class RegisteredProject(ToStringMixin):
Check if the given path matches the project root path.
:param path: the path to check
:return: True if the path matches the project root, False otherwise
:return: True if the path matches the project root, False otherwise (including the case
where this project's root directory no longer exists, e.g. a removed git worktree)
"""
return self.project_root.samefile(Path(path).resolve())
try:
return self.project_root.samefile(Path(path).resolve())
except OSError:
# typically raised if the path does not exist (e.g., a removed git worktree)
return False
def get_project_instance(self, serena_config: "SerenaConfig") -> "Project":
"""
+43
View File
@@ -569,6 +569,49 @@ class TestSerenaConfigFromConfigFileRobustness:
)
class TestGetRegisteredProjectWithDanglingProject:
"""A registered project whose root directory was deleted (e.g. a removed git
worktree) must not break lookup/activation of other, valid projects.
Reproduces the bug where ``get_registered_project`` iterates over every
registered project and ``RegisteredProject.matches_root_path`` raises
``FileNotFoundError`` for the dangling project, aborting the whole lookup.
"""
def setup_method(self):
self.test_dir = Path(tempfile.mkdtemp())
def teardown_method(self):
shutil.rmtree(self.test_dir, ignore_errors=True)
def _make_project_dir(self, name: str) -> Path:
project_dir = self.test_dir / name
(project_dir / SERENA_MANAGED_DIR_NAME).mkdir(parents=True)
(project_dir / SERENA_MANAGED_DIR_NAME / "project.yml").write_text(f'project_name: "{name}"\nlanguages: ["python"]\n')
return project_dir
def test_dangling_project_does_not_break_lookup_of_valid_project(self):
config = create_default_serena_config()
dangling_dir = self._make_project_dir("dangling_project")
valid_dir = self._make_project_dir("valid_project")
# Register the dangling project FIRST so the path-matching loop hits the
# deleted directory before reaching the valid project.
config.projects = [
RegisteredProject.from_project_root(dangling_dir, serena_config=config),
RegisteredProject.from_project_root(valid_dir, serena_config=config),
]
# Delete the dangling project's directory out from under Serena.
shutil.rmtree(dangling_dir)
# Looking up the valid project by path must succeed, not raise
# FileNotFoundError on the deleted dangling project's path.
result = config.get_registered_project(str(valid_dir))
assert result is not None
assert result.project_name == "valid_project"
class TestMemoriesManagerCustomPath:
"""Tests for MemoriesManager with a custom serena data folder."""