diff --git a/plugins/codex-security/scripts/generate_in_scope_files.py b/plugins/codex-security/scripts/generate_in_scope_files.py index 6b5807933..a7e9a5120 100644 --- a/plugins/codex-security/scripts/generate_in_scope_files.py +++ b/plugins/codex-security/scripts/generate_in_scope_files.py @@ -131,9 +131,20 @@ def generate_in_scope_files(repository: Path, scope: str, output: Path) -> int: scope, text=False, ) - except OSError: - tracked = None - if tracked is not None and tracked.returncode == 0: + except OSError as error: + raise InventoryError(f"could not run git ls-files: {error}") from error + + # git_command reports a missing trusted Git executable as status 127 + # with no output. Git is optional for this listing; a Git that ran + # and failed would leave the inventory silently incomplete. + git_missing = tracked.returncode == 127 and not tracked.stderr + if tracked.returncode != 0 and not git_missing: + detail = tracked.stderr.decode("utf-8", errors="replace").strip() + message = f"git ls-files exited with status {tracked.returncode}" + if detail: + message = f"{message}: {detail}" + raise InventoryError(message) + if tracked.returncode == 0: prefix = b"./" if scope == "." or scope.startswith("./") else b"" for path in tracked.stdout.split(b"\0"): candidate = repository / os.fsdecode(path) diff --git a/plugins/codex-security/tests/test_generate_in_scope_files.py b/plugins/codex-security/tests/test_generate_in_scope_files.py index 4dbf9d8de..42aae1cc4 100644 --- a/plugins/codex-security/tests/test_generate_in_scope_files.py +++ b/plugins/codex-security/tests/test_generate_in_scope_files.py @@ -175,6 +175,34 @@ def test_inventory_rejects_line_breaks_before_serializing_paths( assert list(output.parent.glob(f".{output.name}.*.tmp")) == [] +def test_inventory_reports_a_failed_ignored_tracked_listing(tmp_path: Path) -> None: + repository = make_repository(tmp_path) + write_file(repository, "ignored/tracked.py") + git(repository, "add", "--force", "--", "ignored/tracked.py") + git(repository, "config", "core.repositoryformatversion", "1") + git(repository, "config", "extensions.exampleUnsupported", "true") + output = tmp_path / "in_scope_files.txt" + + result = run_inventory(repository, ".", output) + + assert result.returncode == 2, result.stdout + assert "git ls-files" in result.stderr + assert not output.exists() + + +def test_inventory_without_git_keeps_the_ripgrep_listing(tmp_path: Path) -> None: + repository = make_repository(tmp_path) + write_file(repository, "ignored/tracked.py") + git(repository, "add", "--force", "--", "ignored/tracked.py") + output = tmp_path / "in_scope_files.txt" + + # An empty CODEX_SECURITY_GIT selects no Git executable. + result = run_inventory(repository, ".", output, env={**os.environ, "CODEX_SECURITY_GIT": ""}) + + assert result.returncode == 0, result.stderr + assert output.read_bytes() == standard_inventory(repository, ".") + + def test_diff_inventory_includes_power_shell_files( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: