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.
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:
- 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.
- 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
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_graphreply larger than 10 MiB (10,485,760 bytes) makes the server processexit(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 tookcodebase-memory-mcpout entirely and I had to finish by driving the binary over stdio by hand.The boundary is exact and sits one row wide:
The query is an ordinary eight-property projection, nothing exotic:
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
clipath is unaffected. The same query viacodebase-memory-mcp cli query_graphprints 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].textandstructuredContent.textare verbatim duplicates.Measured on the largest surviving response:
content[0].textThe 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_graphcall 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:
f.end_line, f.complexity(values"2","0") stays under, whilef.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.RETURNaccepts at most 32 columns (33+ givesfailed to parse query), so column padding alone cannot get a tiny project there. The repro repo generates 14,000Variablenodes 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.
repro.mjsis dependency-free and speaks MCP over stdio directly, since theclipath does not exhibit the bug.Keep the project name
cbm-payload-crash— it is a prefix of everyqualified_name, so a different name changes the response size slightly. A bareLIMITis 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
.tsfiles,parse_partial_count: 0. Originally hit on a real project of 11,788 files / 63,840 nodes (23,504Variablenodes), where an 8-property projection crosses 10 MiB without anyLIMITat all. 24 GB RAM,budget_mb=8601, build0.9.1-rc.1.Related
query_graph) — also a dead-code query, but Windows, viaOPTIONAL MATCH, and it produces a crash dump. This one is macOS, a plain projection, cleanexit(1)with no dump, and gated purely on response size. Possibly the same underlying exit path reached differently.search_code/ PowerShell) and bug: memory runaway (12.8GB+ RSS) during index_repository on root paths triggers OOM crash #1241 (12.8 GB RSS duringindex_repository) — both indexing-side; this is response-serialization-side.query_graphreply.Confirmations
Posted by Claude Code on behalf of @artaommahe