From 8cb71a2c6f92e9f7123c4f478d8fa19b8fb203a3 Mon Sep 17 00:00:00 2001 From: Dmitry Teryaev Date: Mon, 6 Jul 2026 10:59:42 +0300 Subject: [PATCH] fix(jrag): honor JAVA_CODEBASE_RAG_SOURCE_ROOT over a stray ancestor marker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jrag._resolve_cfg passed source_root=discover_project_root(cwd) into resolve_operator_config, which only honors JAVA_CODEBASE_RAG_SOURCE_ROOT when source_root is None. So any ancestor .java-codebase-rag marker silently overrode the env var that pipeline.subprocess_env sets for the cocoindex child (and that operators/tests rely on), making `jrag imports/outline ` resolve against the wrong root → "file not found", exit 2. Pass source_root=None instead, mirroring cli._resolved_from_ns: resolution is now env var → YAML source_root → discover_project_root(cwd). Interactive use (discovery fallback) is unchanged; the #383 config_source pointer is preserved. Regression test creates a non-empty ancestor marker + sets the env var and asserts the env var wins (fails on pre-fix code, passes with the fix). Co-Authored-By: Claude --- java_codebase_rag/jrag.py | 20 +++++++++++------- tests/test_jrag_status.py | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/java_codebase_rag/jrag.py b/java_codebase_rag/jrag.py index 67d63584..076c5f80 100644 --- a/java_codebase_rag/jrag.py +++ b/java_codebase_rag/jrag.py @@ -1096,20 +1096,26 @@ def _core_parser() -> argparse.ArgumentParser: def _resolve_cfg(args: argparse.Namespace): # type: ignore[no-untyped-def] """Resolve operator config (reuses the operator's cocoindex-free resolver). - Same pattern as ``java_codebase_rag.cli._resolved_from_ns``: walks up from - cwd to find a project root (config file or ``.java-codebase-rag/`` index), - applies CLI ``--index-dir`` if given, and calls ``apply_to_os_environ`` so - downstream modules see a consistent env (critically: SBERT_MODEL for - ``jrag search`` in PR-JRAG-4). + Mirrors ``java_codebase_rag.cli._resolved_from_ns``: pass ``source_root=None`` + so ``resolve_operator_config`` honors ``JAVA_CODEBASE_RAG_SOURCE_ROOT`` first, + then a YAML ``source_root`` field, then walks up from cwd to find a project + root. Passing a discovered root explicitly here would OVERRIDE a set env var + whenever any ancestor dir has a ``.java-codebase-rag`` marker — silently + ignoring the documented subprocess source-root mechanism that + ``pipeline.subprocess_env`` sets for the cocoindex child (and that operators + set directly). When the anchor is an index dir with no YAML beside it, resolution follows that index's ``config_source`` pointer (see ``config._effective_config_dir``) so a config living in a sibling dir is still found from inside a microservice. + Applies CLI ``--index-dir`` if given and calls ``apply_to_os_environ`` so + downstream modules see a consistent env (critically SBERT_MODEL for ``jrag + search``). """ - from java_codebase_rag.config import discover_project_root, resolve_operator_config + from java_codebase_rag.config import resolve_operator_config cfg = resolve_operator_config( - source_root=discover_project_root(Path.cwd()), + source_root=None, cli_index_dir=getattr(args, "index_dir", None), ) cfg.apply_to_os_environ() diff --git a/tests/test_jrag_status.py b/tests/test_jrag_status.py index 7ae24b47..d64a3d06 100644 --- a/tests/test_jrag_status.py +++ b/tests/test_jrag_status.py @@ -21,6 +21,8 @@ import sys from pathlib import Path +import pytest + def _jrag_exe() -> str: """Locate the installed ``jrag`` entry point next to the venv interpreter.""" @@ -172,3 +174,45 @@ def test_jrag_help_lists_status_subcommand() -> None: assert "status" in proc.stdout # The --offset flag must NOT appear in the top-level help. assert "--offset" not in proc.stdout + + +# ----- Config resolution: env var beats a stray ancestor marker ----- + + +def test_resolve_cfg_honors_source_root_env_over_stray_marker( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """JAVA_CODEBASE_RAG_SOURCE_ROOT must win over a stray ``.java-codebase-rag`` + marker in an ancestor of cwd. + + Regression: ``jrag._resolve_cfg`` used to pass ``discover_project_root(cwd)`` + as the explicit ``source_root``, which silently overrode the env var whenever + any ancestor dir had a non-empty ``.java-codebase-rag/`` index — the + documented subprocess source-root mechanism ``pipeline.subprocess_env`` sets + for the cocoindex child (and that the traversal tests rely on). It now passes + ``source_root=None`` so ``resolve_operator_config`` honors the env var first + (mirroring ``cli._resolved_from_ns``). + """ + from java_codebase_rag.jrag import _resolve_cfg + + real_root = tmp_path / "real-source" + real_root.mkdir() + # cwd sits inside a dir whose PARENT has a stray non-empty .java-codebase-rag/ + # index dir — the hijack condition that used to override the env var. + # (_has_index_dir requires the marker dir to be non-empty: any(iterdir()).) + workdir = tmp_path / "has-marker" / "sub" + workdir.mkdir(parents=True) + stray_idx = tmp_path / "has-marker" / ".java-codebase-rag" + stray_idx.mkdir() + (stray_idx / "cocoindex.db").write_bytes(b"") # non-empty → recognized as marker + monkeypatch.chdir(workdir) + monkeypatch.setenv("JAVA_CODEBASE_RAG_SOURCE_ROOT", str(real_root)) + monkeypatch.delenv("JAVA_CODEBASE_RAG_INDEX_DIR", raising=False) + + class _Args: + index_dir = None + + cfg = _resolve_cfg(_Args()) # type: ignore[arg-type] + assert cfg.source_root == real_root.resolve(), ( + "JAVA_CODEBASE_RAG_SOURCE_ROOT must win over a stray ancestor marker" + )