From 64fb17ce9b37e2e99268445bcf0c9cbe4a7a9913 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Fri, 3 Jul 2026 23:33:36 +0300 Subject: [PATCH 1/2] fix(config): don't anchor project root on a stray ~/.java-codebase-rag/ index (#357) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit discover_project_root treated a bare .java-codebase-rag/ index dir at $HOME as a project anchor, so a stray home-level index (e.g. an accidental `init` from home) hijacked resolution for any command run from a $HOME subdir without its own marker — silently reading/writing the home-level index. Demote the index-dir anchor below $HOME only; a config file at $HOME still anchors (a deliberate ~/.java-codebase-rag.yml is intentional). Co-Authored-By: Claude --- java_codebase_rag/config.py | 15 +++++++++++---- tests/test_config.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/java_codebase_rag/config.py b/java_codebase_rag/config.py index 0b7a2de1..0e4e9480 100644 --- a/java_codebase_rag/config.py +++ b/java_codebase_rag/config.py @@ -202,20 +202,27 @@ def discover_project_root(start: Path) -> Path | None: First match wins (closest to start). Config file takes priority over index directory at the same level. Stops at $HOME inclusive — checks $HOME itself but does not walk past it. Returns None if no marker found. + + A bare ``.java-codebase-rag/`` index directory at ``$HOME`` is intentionally + NOT treated as an anchor (issue #357): a stray home-level index (e.g. an + accidental ``init`` run from home) would otherwise hijack resolution for any + command run from a ``$HOME`` subdir without its own marker, silently reading + and writing the home-level index. A config file at ``$HOME`` still anchors. """ start = start.resolve() home = Path.home().resolve() current = start while True: - # Config file is the primary anchor + # Config file is the primary anchor (valid at every level, including $HOME). if find_yaml_config_file(current) is not None: return current - # Index directory is the secondary anchor (supports indexes without config) - if _has_index_dir(current): + # Index directory is the secondary anchor (supports indexes without config), + # but NOT at $HOME — see the docstring for the cross-project hijack rationale. + if current != home and _has_index_dir(current): return current - # Stop if we've reached home (check home itself, but don't walk past it) + # Stop if we've reached home (config-file check above already handled home) if current == home: return None diff --git a/tests/test_config.py b/tests/test_config.py index 0d97a26c..ea260a3b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -136,6 +136,41 @@ def test_discover_project_root_both_markers_same_level(self, tmp_path): result = discover_project_root(tmp_path) assert result == tmp_path + def test_discover_project_root_ignores_stray_index_dir_at_home(self, tmp_path, monkeypatch): + """A bare .java-codebase-rag/ index dir at $HOME must not anchor project + root (issue #357). Otherwise a command run from any $HOME subdir without + its own marker silently resolves to $HOME and reads/writes the home-level + index (cross-project resolution).""" + fake_home = tmp_path / "home" + fake_home.mkdir() + project_dir = fake_home / "project" + project_dir.mkdir() + # Stray index dir at $HOME (e.g. an accidental `init` run from home). + stray_idx = fake_home / ".java-codebase-rag" + stray_idx.mkdir() + (stray_idx / "code_graph.lbug").write_bytes(b"\x00" * 16) + + monkeypatch.setenv("HOME", str(fake_home)) + + result = discover_project_root(project_dir) + assert result is None, "stray ~/.java-codebase-rag/ must not anchor at $HOME (#357)" + + def test_discover_project_root_config_at_home_still_anchors(self, tmp_path, monkeypatch): + """A config file at $HOME still anchors even with a stray index dir beside + it — the #357 fix only demotes the bare index-dir signal at $HOME, not the + config-file anchor (a deliberate ~/.java-codebase-rag.yml is intentional).""" + fake_home = tmp_path / "home" + fake_home.mkdir() + project_dir = fake_home / "project" + project_dir.mkdir() + (fake_home / ".java-codebase-rag").mkdir() # stray index dir + (fake_home / YAML_CONFIG_FILENAMES[0]).write_text("# home config") + + monkeypatch.setenv("HOME", str(fake_home)) + + result = discover_project_root(project_dir) + assert result == fake_home + class TestSourceRootFromYaml: """Tests for source_root YAML field parsing and resolution.""" From b8eae93994a34ce12c694edec59dd5882e995122 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Sat, 4 Jul 2026 08:40:05 +0300 Subject: [PATCH 2/2] test(config): make the stray-home-index scenario non-empty test_discover_project_root_config_at_home_still_anchors created an EMPTY stray ~/.java-codebase-rag/ dir, but _has_index_dir requires non-empty (any(idx.iterdir())), so the index-anchor check never saw it -- the test passed regardless of whether a stray index was actually present and did not represent its own documented "stray index dir beside it" scenario. Write a code_graph.lbug into the stray dir (mirroring the companion ignores_stray_index_dir test) so the fixture is faithful. Co-Authored-By: Claude --- tests/test_config.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_config.py b/tests/test_config.py index ea260a3b..2784a15b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -163,7 +163,14 @@ def test_discover_project_root_config_at_home_still_anchors(self, tmp_path, monk fake_home.mkdir() project_dir = fake_home / "project" project_dir.mkdir() - (fake_home / ".java-codebase-rag").mkdir() # stray index dir + # Stray index dir at $HOME, NON-empty: a real accidental `init` leaves + # code_graph.lbug behind, so _has_index_dir (which requires non-empty) + # actually sees it. An empty mkdir() would be invisible to the index-anchor + # check and would not represent the documented "stray index dir beside it" + # scenario -- mirrors test_discover_project_root_ignores_stray_index_dir_at_home. + stray_idx = fake_home / ".java-codebase-rag" + stray_idx.mkdir() + (stray_idx / "code_graph.lbug").write_bytes(b"\x00" * 16) (fake_home / YAML_CONFIG_FILENAMES[0]).write_text("# home config") monkeypatch.setenv("HOME", str(fake_home))