You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A full index_repository of a single TypeScript monorepo (13,969 files) peaks at
about 11.0 to 11.9 GB RSS on a 16 GB host, and the configured memory budget does not
bound it. The default ram_fraction budget on this box is 3,916 MB and an explicit CBM_MEM_BUDGET_MB=2500 was also honored in the logs but exceeded by roughly 3 to 4x
in both cases. The same binary indexes five smaller repositories on the same box with
memory flat (the largest of those is 34,829 nodes / 100,940 edges in 8.1 s), so this is
specific to scale, not to the install.
I expected the build to either stay near its stated budget or fail with an explanatory
error naming the pass that overshot. Instead it produced three distinct problems, which
I am reporting together because they share the one allocation.
The allocation is not a concurrency artifact. A single-threaded run
(CBM_INDEX_SINGLE_THREAD=1, CBM_MEM_BUDGET_MB=2500) still reached 11.4 GB, and the
worker log attributes it to one pass:
787,665 registry entries cost about 11.4 GB with a single worker, in 2.5 seconds. That
matters for triage: backpressure or worker-count tuning cannot fix a single pass that
wants 11.4 GB on its own, so I do not think this is the same defect as a parallel
over-admission overshoot.
A separate multi-worker run got through extraction and died entering resolve at a
comparable peak:
This reads as "one file wedged the worker", which sent me looking for a bad file for
some time. It is not one file. Across attempts, extraction stalls at a different
position each run: 12,360, then 12,890, then 13,220 of 13,969 files, with no file in
common between the stall points. The position tracks how much memory was already
committed when that run started, not the content at that offset.
If the supervisor can see that RSS is far past budget when it reaps a hang, saying so
in the worker_failed line would remove the entire wrong-turn diagnosis.
Consequence 2: auto_watch=true runs this build inside the serving daemon
auto_watch defaults to true, so the watcher inside the long-lived MCP session daemon
noticed new upstream commits on the watched monorepo and began rebuilding the index in process. The build wants about 11.5 GB, the box had roughly 10 to 11 GB free
with services resident, and the kernel killed the daemon. The supervising service
restarted, a fresh session daemon started, the watcher fired again, and the cycle
repeated. Four kills in fourteen minutes:
Out of memory: Killed process 22295 (codebase-memory) total-vm:17120976kB,
anon-rss:11338244kB ... oom_score_adj:200
Out of memory: Killed process 26839 (codebase-memory) total-vm:17121040kB,
anon-rss:11848124kB ... oom_score_adj:100
Two things make this worse than a failed index. The daemons self-set oom_score_adj
to 200 and 100, which is what kept the kernel selecting them. And because the victim is
the server, every client reaching it got HTTP 502 for roughly ten seconds per restart,
so the visible symptom was an intermittently unavailable MCP server rather than
anything that looked like an indexing problem.
A serving daemon probably should not run an unbounded full build in its own address
space at all. At minimum, an in-daemon watcher rebuild seems like it should be capped,
or run out of process, or be off by default at this repository size.
Consequence 3: the CLI hint points away from the evidence
Every one of these failures surfaces to the caller as only:
{"status":"error","hint":"Pipeline failed. Check repo_path exists and contains source files. ..."}
repo_path existed and contained source files in all of them. The real evidence is in
undocumented files under <cache_dir>/logs/ (cbm-daemon.log and .worker-log-*),
which I found by guessing. Nothing in the CLI output mentions either path.
The knobs that actually resolve this are also undocumented. I found them by running grep -aoE 'CBM_[A-Z0-9_]+' over the 281 MB static binary: CBM_INDEX_SINGLE_THREAD, CBM_MEM_BUDGET_MB, CBM_INDEX_WORKER_TIMEOUT_S, CBM_DISABLE_LSP_CROSS, CBM_INDEX_MAX_RESTARTS, CBM_MAX_FILE_BYTES, CBM_RETAIN_PER_FILE_MB, CBM_RETAIN_TOTAL_MB, CBM_SEMANTIC_ENABLED and others. The config command exposes
only auto_index, auto_index_limit, auto_watch, ui-lang, ui_enabled and ui_port, none of which touch memory. Documenting the first three would have saved the
whole investigation.
What works
Stated plainly, because it narrows the problem:
CBM_INDEX_SINGLE_THREAD=1 with about 15 GB free (every other service stopped)
completes the identical full index in 1m33s, status=indexed, 164,323 nodes and
834,119 edges, both matching the expected counts, parse_partial_count 278, six
indexes and none quarantined. One caveat so the timing is not overread: that run
reused cached extraction from the preceding failed attempts, so 1m33s is not a
cold-start number. The memory behavior is the point, not the speed.
mode: fast succeeds on the same repository.
The five smaller repositories on the same box index normally, memory flat throughout.
So the build is entirely capable of completing. It just needs about 11.5 GB of headroom
that neither budget setting reserves or enforces, and nothing in the product tells an
operator that in advance.
Reproduction
I cannot share this tree, and I want to be straight about that rather than imply a
public repro I have not run. What I can characterize precisely:
Code being indexed: a private TypeScript monorepo, 13,969 files, producing
787,665 registry entries, 164,323 nodes and 834,119 edges. Ordinary application and
library TypeScript with dense cross-package imports; no generated blobs, no vendored
trees, nothing that .gitignore should have excluded.
Result: with services resident, either a supervisor hang (multi-worker) or a
kernel OOM kill, at 11.0 to 11.9 GB RSS in both cases, against budget_mb=3916 by
default or 2,500 when set explicitly. With CBM_INDEX_SINGLE_THREAD=1 and about
15 GB genuinely free, success in 1m33s.
Expected: the build respects CBM_MEM_BUDGET_MB, or fails with an error naming registry_build and the observed RSS.
My guess at a public equivalent, untested: any TypeScript monorepo in the 12k to 15k
file range with heavy cross-package imports, indexed on a host with under about 16 GB
free, should show the same mem.phase phase=registry_build peak. If it would help, tell
me which public repository you would like measured and I will run it on this box and
post the phase table.
Diagnostics trajectory
Not captured during these runs, since I did not know CBM_DIAGNOSTICS=1 existed until
after the fact. I can reproduce with it set and attach the ndjson on request; the
repository and the box are both still available, and I can also test a patch or a build
with extra mem.phase instrumentation.
I searched existing issues. This overlaps several open reports but I do not believe it
is a duplicate of any of them, so I am listing what I found and why I filed separately.
Please close this into whichever one you prefer if you disagree.
My reproduction does not use a public repository. It is a private monorepo and I
have not managed to reduce it to a shareable case, since the trigger appears to be
aggregate scale rather than any single construct. I have described the scale and the
environment as precisely as I can above, and I would rather say so than check a box
that is not true. Glad to run whatever public repository you nominate.
Same deployment as #1991 (the per-request latency floor), if that is useful context for
the environment. Thanks for the work on this project.
Version
codebase-memory-mcp 0.10.8 (aarch64 static build)
Platform
Linux (arm64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
A full
index_repositoryof a single TypeScript monorepo (13,969 files) peaks atabout 11.0 to 11.9 GB RSS on a 16 GB host, and the configured memory budget does not
bound it. The default
ram_fractionbudget on this box is 3,916 MB and an explicitCBM_MEM_BUDGET_MB=2500was also honored in the logs but exceeded by roughly 3 to 4xin both cases. The same binary indexes five smaller repositories on the same box with
memory flat (the largest of those is 34,829 nodes / 100,940 edges in 8.1 s), so this is
specific to scale, not to the install.
I expected the build to either stay near its stated budget or fail with an explanatory
error naming the pass that overshot. Instead it produced three distinct problems, which
I am reporting together because they share the one allocation.
The allocation is not a concurrency artifact. A single-threaded run
(
CBM_INDEX_SINGLE_THREAD=1,CBM_MEM_BUDGET_MB=2500) still reached 11.4 GB, and theworker log attributes it to one pass:
787,665 registry entries cost about 11.4 GB with a single worker, in 2.5 seconds. That
matters for triage: backpressure or worker-count tuning cannot fix a single pass that
wants 11.4 GB on its own, so I do not think this is the same defect as a parallel
over-admission overshoot.
A separate multi-worker run got through extraction and died entering resolve at a
comparable peak:
with the daemon having started under:
Consequence 1: multi-worker mode hangs rather than failing
When memory pressure stalls forward progress, the supervisor does not surface an
out-of-memory condition. It times the worker out and reports a hang:
This reads as "one file wedged the worker", which sent me looking for a bad file for
some time. It is not one file. Across attempts, extraction stalls at a different
position each run: 12,360, then 12,890, then 13,220 of 13,969 files, with no file in
common between the stall points. The position tracks how much memory was already
committed when that run started, not the content at that offset.
If the supervisor can see that RSS is far past budget when it reaps a hang, saying so
in the
worker_failedline would remove the entire wrong-turn diagnosis.Consequence 2:
auto_watch=trueruns this build inside the serving daemonauto_watchdefaults to true, so the watcher inside the long-lived MCP session daemonnoticed new upstream commits on the watched monorepo and began rebuilding the index
in process. The build wants about 11.5 GB, the box had roughly 10 to 11 GB free
with services resident, and the kernel killed the daemon. The supervising service
restarted, a fresh session daemon started, the watcher fired again, and the cycle
repeated. Four kills in fourteen minutes:
Two things make this worse than a failed index. The daemons self-set
oom_score_adjto 200 and 100, which is what kept the kernel selecting them. And because the victim is
the server, every client reaching it got HTTP 502 for roughly ten seconds per restart,
so the visible symptom was an intermittently unavailable MCP server rather than
anything that looked like an indexing problem.
A serving daemon probably should not run an unbounded full build in its own address
space at all. At minimum, an in-daemon watcher rebuild seems like it should be capped,
or run out of process, or be off by default at this repository size.
Consequence 3: the CLI hint points away from the evidence
Every one of these failures surfaces to the caller as only:
repo_pathexisted and contained source files in all of them. The real evidence is inundocumented files under
<cache_dir>/logs/(cbm-daemon.logand.worker-log-*),which I found by guessing. Nothing in the CLI output mentions either path.
The knobs that actually resolve this are also undocumented. I found them by running
grep -aoE 'CBM_[A-Z0-9_]+'over the 281 MB static binary:CBM_INDEX_SINGLE_THREAD,CBM_MEM_BUDGET_MB,CBM_INDEX_WORKER_TIMEOUT_S,CBM_DISABLE_LSP_CROSS,CBM_INDEX_MAX_RESTARTS,CBM_MAX_FILE_BYTES,CBM_RETAIN_PER_FILE_MB,CBM_RETAIN_TOTAL_MB,CBM_SEMANTIC_ENABLEDand others. Theconfigcommand exposesonly
auto_index,auto_index_limit,auto_watch,ui-lang,ui_enabledandui_port, none of which touch memory. Documenting the first three would have saved thewhole investigation.
What works
Stated plainly, because it narrows the problem:
CBM_INDEX_SINGLE_THREAD=1with about 15 GB free (every other service stopped)completes the identical full index in 1m33s,
status=indexed, 164,323 nodes and834,119 edges, both matching the expected counts,
parse_partial_count278, sixindexes and none quarantined. One caveat so the timing is not overread: that run
reused cached extraction from the preceding failed attempts, so 1m33s is not a
cold-start number. The memory behavior is the point, not the speed.
mode: fastsucceeds on the same repository.So the build is entirely capable of completing. It just needs about 11.5 GB of headroom
that neither budget setting reserves or enforces, and nothing in the product tells an
operator that in advance.
Reproduction
I cannot share this tree, and I want to be straight about that rather than imply a
public repro I have not run. What I can characterize precisely:
Code being indexed: a private TypeScript monorepo, 13,969 files, producing
787,665 registry entries, 164,323 nodes and 834,119 edges. Ordinary application and
library TypeScript with dense cross-package imports; no generated blobs, no vendored
trees, nothing that
.gitignoreshould have excluded.Host: 16 GB RAM (
total_ram_mb=15665), 4 vCPU, aarch64, Linux.Command:
Result: with services resident, either a supervisor hang (multi-worker) or a
kernel OOM kill, at 11.0 to 11.9 GB RSS in both cases, against
budget_mb=3916bydefault or 2,500 when set explicitly. With
CBM_INDEX_SINGLE_THREAD=1and about15 GB genuinely free, success in 1m33s.
Expected: the build respects
CBM_MEM_BUDGET_MB, or fails with an error namingregistry_buildand the observed RSS.My guess at a public equivalent, untested: any TypeScript monorepo in the 12k to 15k
file range with heavy cross-package imports, indexed on a host with under about 16 GB
free, should show the same
mem.phase phase=registry_buildpeak. If it would help, tellme which public repository you would like measured and I will run it on this box and
post the phase table.
Diagnostics trajectory
Not captured during these runs, since I did not know
CBM_DIAGNOSTICS=1existed untilafter the fact. I can reproduce with it set and attach the ndjson on request; the
repository and the box are both still available, and I can also test a patch or a build
with extra
mem.phaseinstrumentation.Project scale
164,323 nodes / 834,119 edges / 13,969 files (787,665 registry entries)
Confirmations
is a duplicate of any of them, so I am listing what I found and why I filed separately.
Please close this into whichever one you prefer if you disagree.
same version, same platform class, same "budget is advisory" conclusion. It differs
in the loop mechanism, which there is
invalid_existing_dbretriggering a fullreindex on a schedule, and here is
auto_watchrebuilding inside the servingdaemon. It also has no phase attribution, whereas the single-threaded 11.4 GB
registry_buildmeasurement above says the overshoot is one pass rather thanparallel over-admission. Happy to have this merged into Daily OOM crash-loop: worker RSS far exceeds its own memory budget, then full reindex retriggers on every crash #1864 if you read it as one
defect; the
registry_buildand single-thread numbers are the part I would wantcarried over.
mem.backpressure.futiletosoft_overshoot) and also notes the caller sees nothing useful. Its over-collectionroot cause is a nested
.gitignorediscovery bug, so it indexes files it shouldhave skipped. This repository has no such excess; the 13,969 files are all files we
intend to index.
lsp_cross/parallel.resolveunbounded RAM but is PHP specific,and argues the TypeScript resolver self-limits. The
lsp_cross_preparepeak aboveis TypeScript, which may be worth a look in that context, though
registry_buildisthe larger allocation here.
auto_watchloop, but there the watcher fires with noreal changes. Here the watcher was correct that upstream commits existed; the defect
is the cost and the location of the rebuild it triggers, not the detection.
files. Here the stall position moves between runs with no file in common.
have not managed to reduce it to a shareable case, since the trigger appears to be
aggregate scale rather than any single construct. I have described the scale and the
environment as precisely as I can above, and I would rather say so than check a box
that is not true. Glad to run whatever public repository you nominate.
Same deployment as #1991 (the per-request latency floor), if that is useful context for
the environment. Thanks for the work on this project.