Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,24 @@ all tools are available. This is especially useful for MCP client configurations

## Troubleshooting

### `pip` / `pipx` cannot download `hatchling` (or `Errno 9` / `Bad file descriptor` to PyPI)

Installing from a **source tree** (for example `pipx install .`) needs build dependencies from **PyPI** (for example `hatchling`). If you see `Could not find a version that satisfies the requirement hatchling` after connection warnings, the Python/pip in that **terminal** may not be able to open an HTTPS client to `pypi.org` (sometimes seen in an integrated editor terminal; less often system-wide with VPN, firewall, or proxy).

**Options:**

1. Run the same command from **macOS Terminal.app** (or iTerm) instead of the IDE’s terminal, then retry `pipx install .` or `pipx install "git+https://..."` .
2. Use **[uv](https://docs.astral.sh/uv/)** to install the CLI from a checkout (uses different download machinery than `pip` in many cases):

```bash
cd /path/to/code-review-graph
uv tool install . --force
```

3. For **development in a clone** without a global install, use `uv sync` and `uv run code-review-graph …` (or activate `.venv` after `uv sync`).

**Diagnose (optional):** `python3 scripts/diagnose_pypi_connectivity.py` — if it prints `FAILED`, the issue is environment/network, not a wrong package name in this repo.

### Windows Configuration Issues (Invalid JSON / Connection Closed)
If you are using Windows and encounter `Invalid JSON: EOF while parsing` or `MCP error -32000: Connection closed` when connecting via Claude Code, do not use the `cmd /c` wrapper in your config.

Expand Down
11 changes: 7 additions & 4 deletions code_review_graph/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ def _resolve_repo_root(repo_root: Optional[str]) -> Optional[str]:
(captured in ``_default_repo_root``).
3. None — the underlying impl will fall back to the server's cwd.

Previously, only ``get_docs_section_tool`` consulted ``_default_repo_root``,
so ``serve --repo <X>`` had no effect for the other 21 tools. See: #222
follow-up.
All MCP tools that accept ``repo_root`` should use this helper so
``serve --repo <X>`` applies consistently, including
``get_docs_section_tool``. See: #222.
"""
return repo_root if repo_root else _default_repo_root

Expand Down Expand Up @@ -378,7 +378,10 @@ def get_docs_section_tool(
section_name: The section to retrieve (e.g. "review-delta", "usage").
repo_root: Repository root path. Auto-detected if omitted.
"""
return get_docs_section(section_name=section_name, repo_root=repo_root)
return get_docs_section(
section_name=section_name,
repo_root=_resolve_repo_root(repo_root),
)


@mcp.tool()
Expand Down
18 changes: 8 additions & 10 deletions code_review_graph/tools/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from typing import Any

from ..embeddings import EmbeddingStore, embed_all_nodes
from ..incremental import get_db_path
from ._common import _get_store
from ..incremental import find_project_root, get_db_path
from ._common import _get_store, _validate_repo_root

# ---------------------------------------------------------------------------
# Tool 7: embed_graph
Expand Down Expand Up @@ -110,14 +110,12 @@ def get_docs_section(
search_roots: list[Path] = []

if repo_root:
search_roots.append(Path(repo_root))

try:
_, root = _get_store(repo_root)
if root not in search_roots:
search_roots.append(root)
except (RuntimeError, ValueError):
pass
try:
search_roots.append(_validate_repo_root(Path(repo_root)))
except ValueError:
pass
else:
search_roots.append(find_project_root())

# Fallback: package directory (for uvx/pip installs)
pkg_docs = (
Expand Down
62 changes: 62 additions & 0 deletions scripts/diagnose_pypi_connectivity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Check whether this Python can reach PyPI (same path pip/pipx use for hatchling, etc.).

If TLS to pypi.org fails (e.g. Errno 9 in some IDE terminals), a user-wide
install from a git checkout may still work via uv (different downloader):

uv tool install /path/to/code-review-graph --force

Run: python3 scripts/diagnose_pypi_connectivity.py
"""
from __future__ import annotations

import json
import os
import socket
import ssl
import sys
import time
import urllib.error
import urllib.request


def main() -> int:
ok_tls = _try_tls_pypi()
ok_url = _try_urllib()
if ok_tls and ok_url:
print("PyPI check: OK (this Python can use HTTPS to pypi.org).")
return 0
print("PyPI check: FAILED (pip/pipx may be unable to download build deps like hatchling).")
print("Workaround: from the repo root, with https://github.com/astral-sh/uv installed:")
print(' uv tool install . --force')
print("Or run pipx from macOS Terminal.app (outside the IDE) if the failure is terminal-specific.")
return 1


def _try_tls_pypi() -> bool:
try:
ctx = ssl.create_default_context()
with socket.create_connection(("pypi.org", 443), timeout=15) as sock:
with ctx.wrap_socket(sock, server_hostname="pypi.org") as tsock:
return bool(tsock.version())
except OSError as e:
print(f" TLS pypi.org:443 -> {e!r}", file=sys.stderr)
return False


def _try_urllib() -> bool:
try:
req = urllib.request.Request(
"https://pypi.org/simple/hatchling/",
headers={"User-Agent": "code-review-graph-diagnostic/1.0"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
resp.read(256)
return True
except (urllib.error.URLError, OSError) as e:
print(f" urllib hatchling index -> {e!r}", file=sys.stderr)
return False


if __name__ == "__main__":
raise SystemExit(main())
1 change: 1 addition & 0 deletions tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class TestGetDocsSection:
"""Tests for the get_docs_section tool."""

def test_explicit_repo_root_uses_that_docs_file(self, tmp_path):
(tmp_path / ".code-review-graph").mkdir()
docs_dir = tmp_path / "docs"
docs_dir.mkdir()
(docs_dir / "LLM-OPTIMIZED-REFERENCE.md").write_text(
Expand Down