Pod

Available as Markdown and JSON. Pod is also available over MCP.

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.

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.

# 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 · 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:

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 · 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:

(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 · 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 · 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 · 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

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 · 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 · 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 · 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:

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 · 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:

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 · 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:

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 · 2026-09-19 · closed · 1 comment

The remaining reports are on the project's issue tracker.