# Reported issues for repo2graph

Pod holds 16 of 26 GitHub reports that passed its relevance review. This can include external user reports, maintainer-confirmed bugs, and concrete feature gaps. Treat them as evidence to inspect, not a count of distinct defects.

Back to [repo2graph](/mcp/repo2graph).

## Most discussed

### [P2] Update manifest.json checksums when redrawing graph.html in cmd_map

## Problem
When running `repo2graph map -o <out_dir>` to re-render the visualization (`human/graph.html`) of an existing graph, `cli.py:cmd_map` loads `graph.json`, generates HTML, and calls `write_html(..., html, ...)`.

However, unlike `cmd_build` and `export.py`, `cmd_map` never updates or re-registers the file checksum with `agent/manifest.json`.

```python
# repo2graph/cli.py:644-648
html = render_html(graph_data, title=args.title)
write_html(html_path, html, minify=not…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/339) · 2026-09-22 · closed · 1 comment

### [P1] Make HTTP auto-build disabled by default

## Problem
A "read-only" MCP tool request can trigger a full local build, artifact write, parser execution, and Git interaction.

## Required changes
- Preserve auto-build for local stdio only if necessary.
- Make HTTP mode require explicit `--allow-auto-build` or equivalent.
- Add separate build authorization/capability if HTTP auto-build remains available.
- Return an actionable "index not available" response containing expected build instructions.

## Acceptance criteria
- Starting HTTP MCP…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/265) · 2026-09-21 · closed · 1 comment

### http_server: _read_body honours Content-Length only, leaving a chunked request body unread in the socket

**Problem.** `_read_body` (`repo2graph/http_server.py:277-285`) reads exactly what `Content-Length` declares:

```python
def _read_body(self) -> bytes:
    try:
        length = int(self.headers.get("Content-Length") or 0)
    except ValueError:
        raise AuthError("invalid Content-Length", status=400) from None
    if length < 0 or length > MAX_BODY_BYTES:
        raise AuthError(f"request body must be at most {MAX_BODY_BYTES} bytes", status=413)
    return self.rfile.read(length) if…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/249) · 2026-09-19 · closed · 1 comment

### auth: a JWK with kty RSA but no n or e raises KeyError out of decode_jwt

**Problem.** `decode_jwt` (`repo2graph/auth.py:459-473`) checks the key type and the declared algorithm, then indexes the JWK's RSA parameters directly:

```python
key = jwks.key_for(str(kid))
if key.get("kty") != "RSA":
    raise AuthError(f"unsupported key type {key.get('kty')!r}")
...
if not rsa_verify(
    _int_from_b64url(str(key["n"])),     # <- KeyError if absent
    _int_from_b64url(str(key["e"])),     # <- KeyError if absent
    ...
```

A key set advertising `{"kid": "k1", "kty":…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/238) · 2026-09-19 · closed · 1 comment

### audit: sanitize_params recurses without a depth limit, so nested tool arguments raise RecursionError out of the unguarded _reject path

**Problem.** `sanitize_value` (`repo2graph/audit.py:142-144`) recurses into containers with no depth bound:

```python
if isinstance(value, dict):
    return {k: sanitize_value(str(k), v) for k, v in value.items()}
if isinstance(value, (list, tuple)):
    return [sanitize_value(key, v) for v in value]
```

Verified:

```
$ python -c "
from repo2graph.audit import sanitize_params
d = {'a': 1}
for _ in range(3000): d = {'k': d}
sanitize_params(d)"
RecursionError: maximum recursion depth exceeded…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/234) · 2026-09-19 · closed · 1 comment

### http_server: a deeply nested JSON body raises RecursionError past `except ValueError`, killing the handler thread pre-auth

**Problem.** `do_POST` (`repo2graph/http_server.py:383-387`) parses the request body like this:

```python
try:
    request = json.loads(raw.decode("utf8", "replace")) if raw else None
except ValueError:
    self._send_json(400, _rpc_error(None, PARSE_ERROR, "invalid JSON"))
    return
```

`json.loads` raises `RecursionError` — not a `ValueError` — on a deeply nested document, so that guard does not catch it. Verified:

```
$ python -c "
import json
for n in (2000, 20000):
    try:…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/233) · 2026-09-19 · closed · 1 comment

### auth: a non-ASCII bearer credential raises TypeError out of authenticate(), killing the HTTP handler thread before auth resolves

**Problem.** `Authenticator.authenticate` (`repo2graph/auth.py:591`) compares the caller's credential against the static token with `hmac.compare_digest`:

```python
if self.config.token:
    if hmac.compare_digest(credential, self.config.token):
        return Identity(subject="bearer", mode="bearer")
```

`hmac.compare_digest` refuses `str` operands that are not ASCII-only — it raises `TypeError`, not a mismatch. The credential comes straight off the `Authorization` header, so any client that…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/232) · 2026-09-19 · closed · 1 comment

### prod-igy: issue_comment trigger has no author_association gate, and branch names are interpolated into the bot comment

**Problem (no author gate).** `prod-igy.yml` fires the privileged `triage` job on `issue_comment`:

```yaml
(github.event_name == 'issue_comment' && github.event.issue.pull_request &&
 (contains(github.event.comment.body, '@prod-igy') || contains(github.event.comment.body, '/prod-igy')))
```

and the job holds `pull-requests: write`, `issues: write` and the `PRODIGY_APP_ID` installation token. Neither the workflow `if:` nor `prod-igy.js`'s `issue_comment` handler…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/208) · 2026-09-19 · closed · 1 comment

## Most recent

### mcp 1.x is documented as supported but hangs on the first tool call (the #90 hang)

Found while switching CI to the committed lockfile in #406.

## What happens

Under **mcp 1.30.0**, the MCP server never answers its first tool call on the parallel path:

```
FAILED tests/test_mcp.py::test_iss90_tools_call_over_serve_completes_on_the_parallel_path
AssertionError: no JSON-RPC response within 240s (the #90 hang)
```

Deterministic — reproduced 2/2, ~4 minutes each. Under **mcp 2.2.0** the same test passes in 1.9s.

## Why it matters

1.x is not an accident of resolution, it is…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/407) · 2026-09-22 · open · 0 comments

### [P2] HTTP transport advertises protocolVersion 2025-06-18 but does not implement Streamable HTTP

## Summary

`initialize` responds with `"protocolVersion": "2025-06-18"`. That revision's HTTP transport **is** Streamable HTTP: a single endpoint supporting `POST` for requests and `GET` for an SSE stream, an `Mcp-Session-Id` header for session binding, `Last-Event-ID` for resumability, and `text/event-stream` responses when the server chooses to stream.

What is implemented is a plain JSON-RPC-over-`POST` endpoint: `Content-Length` framing only, `HTTP/1.0` with `close_connection` after every…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/390) · 2026-09-22 · open · 0 comments

### [P2] Warn when the index is stale relative to the working tree

## Summary

The MCP server detects that the index was **rebuilt by someone else** — `_index_mtime` compares `manifest.json`'s mtime and reloads. It does not detect that the **working tree moved underneath the index**. A long-running server therefore answers from a stale graph indefinitely, and says nothing.

## Evidence

`repo2graph/mcp.py:272-276`

```python
def _index_mtime(out_path: Path) -> float:
    ...
    return target.stat().st_mtime if target.is_file() else 0.0
```

and `:338-341`…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/383) · 2026-09-22 · open · 0 comments

### [P2] Apply the Host and Origin check to GET and HEAD, not only POST

## Summary

`do_POST` and `do_OPTIONS` both validate `Host` and `Origin` before anything else runs — exactly right, and the module docstring explains why (DNS rebinding against a loopback bind). `do_GET` and `do_HEAD` do not.

The worst of this was fixed by fb903b7 (`_public_repo_label` publishes the basename, not the absolute path). What remains is that a page open in the user's browser can still read `/.well-known/mcp-server-metadata` and `/healthz` cross-origin: the repo basename, whether an…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/372) · 2026-09-22 · open · 0 comments

### [P2] Enforce a minimum RSA modulus size and bound the public exponent in rsa_verify

## Summary

`rsa_verify` rejects `n <= 0` and `e <= 0`, then proceeds for any value above that. Two consequences:

1. **No minimum modulus.** A 512-bit RSA key published in a JWKS verifies happily. The signature maths is correct; the key is simply not strong enough to mean anything.
2. **Unbounded exponent.** `pow(int.from_bytes(signature, "big"), e, n)` with a hostile multi-thousand-bit `e` is a CPU sink, reachable once per request on the `decode_jwt` path. `MAX_JWKS_BYTES` (1 MiB) bounds the…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/367) · 2026-09-22 · open · 0 comments

### mcp: --http-only without --http-port silently serves stdio, and audit.close() is unreachable on that path

**Two defects on the same code path** (`repo2graph/mcp.py:920-955`), both stemming from `--http-only` living *inside* the block that builds the transport.

**1. `--http-only` with no `--http-port` silently serves stdio.** The transport is only constructed under:

```python
if args.http_port is not None or args.auth_cimd:
    ...
    transport.start()
    if args.http_only:
        ...
        return 0
elif auth_config.enabled:
    raise SystemExit(...)
```

With `--http-only` alone, neither…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/203) · 2026-09-19 · closed · 1 comment

### audit: the high_entropy rule redacts ordinary identifier queries out of the audit log

**Problem.** `_looks_like_a_secret` (`repo2graph/audit.py:113-120`) treats any long unbroken run of token characters as a credential:

```python
if len(value) >= ENTROPY_MIN_LEN and re.fullmatch(r"[A-Za-z0-9+/=_-]+", value):
    digits = sum(c.isdigit() for c in value)
    letters = sum(c.isalpha() for c in value)
    if digits and letters:
        return "high_entropy"
```

`_` is in that character class, so a snake_case identifier of 24+ characters containing at least one digit matches.…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/202) · 2026-09-19 · closed · 1 comment

### http_server: _send_json misses ConnectionAbortedError and leaves end_headers() outside its guard, leaking tracebacks into the JSON-lines stderr stream

**Problem.** `_send_json` (`repo2graph/http_server.py:160-174`) guards only the body write, and only against two of the several exceptions a disconnect can raise:

```python
self.send_response(status)
self.send_header(...)
self.end_headers()          # <- flushes; outside the try
try:
    self.wfile.write(body)
except (BrokenPipeError, ConnectionResetError):
    return
```

Two gaps:

1. `ConnectionAbortedError` is neither of the two caught types (all three are siblings under…

[Read the thread](https://github.com/Srinivasan-78/repo2graph/issues/199) · 2026-09-19 · closed · 1 comment

The remaining reports are on [the project's issue tracker](https://github.com/Srinivasan-78/repo2graph/issues).
