Skip to content

query_graph: MCP server exits(1) with no diagnostics when a response exceeds 10 MiB — and every reply duplicates its payload, halving the usable budget #1375

Description

@artaommahe

Version

codebase-memory-mcp 0.9.1-rc.1

Platform

macOS (Apple Silicon)

Install channel

GitHub release archive / install.sh / install.ps1

Binary variant

standard

What happened, and what did you expect?

A single query_graph reply larger than 10 MiB (10,485,760 bytes) makes the server process exit(1). There is no JSON-RPC error, nothing on stderr, and no macOS crash report — the client only sees the connection close. Under an MCP host that does not respawn the server, every tool from that server is gone for the rest of the session, which is how I hit it: one dead-code query mid-investigation took codebase-memory-mcp out entirely and I had to finish by driving the binary over stdio by hand.

The boundary is exact and sits one row wide:

LIMIT 13857 -> ok, response=10485320 bytes   (440 bytes below 10 MiB)
LIMIT 13858 -> SERVER DIED exit=1 stderr=<empty>

The query is an ordinary eight-property projection, nothing exotic:

MATCH (f:Variable) RETURN f.qualified_name, f.name, f.file_path, f.is_exported,
                          f.is_test, f.is_entry_point, f.start_line, f.lines

Deterministic — 6/6 at the same size, reproduced from both a Node client and a Python client, with a permanent daemon running and without one, and with no other concurrent servers. Exit code 1 with no signal, so this is a deliberate exit path rather than a segfault.

The cli path is unaffected. The same query via codebase-memory-mcp cli query_graph prints all 14,000 rows and exits 0. Only the MCP JSON-RPC response path dies, which is why this only bites MCP hosts.

Expected: truncate with a notice, return a JSON-RPC error, or paginate. Anything but exiting the process — a silent exit(1) is indistinguishable from a transport fault, and it costs the session every other tool on the server.

Every reply also carries its payload twice, which halves the usable budget

While measuring the boundary I found the reason the limit is reached at only ~5 MB of actual content: content[0].text and structuredContent.text are verbatim duplicates.

// result of a 3-row query
{
  "content": [{ "type": "text", "text": "rows: 3  (cols: f.name)\n  generated...\ntotal: 3\n" }],
  "structuredContent": { "text": "rows: 3  (cols: f.name)\n  generated...\ntotal: 3\n" },
  "isError": false
}

Measured on the largest surviving response:

measurement bytes
decoded content[0].text 5,090,170
that text, JSON-escaped 5,242,601
full JSON-RPC line 10,485,319
10 MiB 10,485,760

The full line is exactly 2.000x the escaped text; JSON escaping itself accounts for only 1.03x. So half of every reply is redundant bytes, the effective result ceiling is ~5 MB rather than 10 MiB, and (for LLM callers) every query_graph call costs double the tokens it needs to. Worth considering alongside #1301.

Happy to split the duplication into its own issue if you'd prefer them tracked separately — filing together because the first is only reachable at half the expected size because of the second.

Two things that make this easy to misdiagnose

Both cost me time, so flagging them for whoever picks this up:

  1. The trigger is the serialized size, not the row count or the column count. Swapping one column for another at an identical row count can cross the boundary: projecting f.end_line, f.complexity (values "2", "0") stays under, while f.is_test, f.is_entry_point (values "false") adds ~150 KB over 14,000 rows and goes over. Two 8-column queries of near-identical shape, one fine and one fatal, reads as flakiness or memory pressure when it is neither.
  2. It will not reproduce on a small repo. You need enough nodes to serialize >10 MiB, and RETURN accepts at most 32 columns (33+ gives failed to parse query), so column padding alone cannot get a tiny project there. The repro repo generates 14,000 Variable nodes for this reason.

Reproduction

Public repro repo: https://github.com/artaommahe/codebase-memory-mcp-payload-limit-repro — all generated code, nothing proprietary, verified from a clean clone.

git clone https://github.com/artaommahe/codebase-memory-mcp-payload-limit-repro
cd codebase-memory-mcp-payload-limit-repro
codebase-memory-mcp cli index_repository --repo-path "$PWD" --mode full --name cbm-payload-crash

node repro.mjs            # control just under the limit, then the fatal case
node repro.mjs --bisect    # walks the byte boundary one row at a time

repro.mjs is dependency-free and speaks MCP over stdio directly, since the cli path does not exhibit the bug.

Control - LIMIT 13000, comfortably under 10 MiB:
  ok, response=9836678 bytes (93.81% of 10 MiB)

Bug - no LIMIT, all 14,000 nodes (~10.6 MiB):
  SERVER DIED exit=1 stderr=<empty>

Keep the project name cbm-payload-crash — it is a prefix of every qualified_name, so a different name changes the response size slightly. A bare LIMIT is also not a stable subset, so the exact row number where it flips moves by a few rows between indexing runs; the size boundary does not.

Workaround

Pass max_rows. It is applied before serialization, so a capped query of any size is safe. Worth documenting until the limit is handled, since the natural "dump a label and diff locally" workflow hits this immediately on a large repo.

Project scale (if relevant)

Repro repo: 14,174 nodes / 14,162 edges, 70 generated .ts files, parse_partial_count: 0. Originally hit on a real project of 11,788 files / 63,840 nodes (23,504 Variable nodes), where an 8-property projection crosses 10 MiB without any LIMIT at all. 24 GB RAM, budget_mb=8601, build 0.9.1-rc.1.

Related

Confirmations

  • I searched existing issues and this is not a duplicate.
  • My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.

Posted by Claude Code on behalf of @artaommahe

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.stability/performanceServer crashes, OOM, hangs, high CPU/memoryux/behaviorDisplay bugs, docs, adoption UX

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions