Summary
Indexing a repository containing a specific — and perfectly valid — PHP pattern makes an extract worker allocate memory without bound (observed 29 GB RSS on a single worker, ~100 GB system-wide including swap/compression before the memory watchdog SIGKILLs the process group). The run always dies with exit 137 shortly before extraction completes, because the affected files are the slowest and finish last.
A stack sample of the ballooning worker shows it spinning inside the trait flattener:
cbm_extract_file
cbm_run_php_lsp
php_lsp_collect_class_fields
process_class_for_fields
flatten_trait_into_class <- ~100% of samples, allocating via cbm_arena_strdup/cbm_arena_sprintf
Root cause (from behavior)
Trait use resolution appears to ignore namespaces and aliases and resolve by short name. When a class has the same short name as a trait it imports under an alias, the resolver picks the class itself as the trait to flatten, and flatten_trait_into_class recurses/loops forever while copying fields.
Minimal reproduction (2 files, 15 lines)
src/delivery-trait.php:
<?php
namespace App\Functions;
trait Delivery {
public function send( string $payload ): bool {
return true;
}
}
src/delivery-class.php:
<?php
namespace App\Modules\Foo;
use App\Functions\Delivery as Delivery_Trait;
class Delivery {
use Delivery_Trait;
public function run(): void {
}
}
Steps:
mkdir -p repro/src && cd repro && git init
# add the two files above
codebase-memory-mcp cli index_repository '{"repo_path": "<abs path>/repro", "mode": "fast"}'
Result: the worker's RSS grows until the memory watchdog kills the process group; the CLI exits with code 137. Remove the alias (or rename the class so it no longer collides with the trait's short name) and the same repo indexes in milliseconds.
Note this is idiomatic PHP: use Vendor\Traits\Delivery as Delivery_Trait; inside class Delivery is a common way to disambiguate, and PHP itself resolves it correctly.
Environment
- codebase-memory-mcp v0.8.1 (latest release at time of writing)
- macOS (Darwin 25.5.0), arm64, 64 GB RAM
- Real-world hit: a WordPress codebase where three module classes named
Delivery import a shared trait Delivery from another namespace via use ... as Delivery_Trait;
Impact / workaround
- MCP-server-driven
index_repository takes the whole MCP server down with it when the watchdog fires.
CBM_WORKERS=1, CBM_DISABLE_LSP_CROSS=1 and CBM_LSP_DISABLED=1 do not prevent it (tested).
- Current workaround: exclude the colliding class files via
.cbmignore.
Suggested fixes
- Resolve trait
use statements through the file's import table (aliases + namespaces) instead of short names, and never resolve a use inside a class body to a class.
- As a safety net, add a depth/visited-set guard in
flatten_trait_into_class so any resolution cycle degrades gracefully instead of allocating until the watchdog kills the run.
May relate to the memory reports in #581 / #593 / #832, but this one is a deterministic infinite loop with a tiny repro rather than gradual growth/retention.
Summary
Indexing a repository containing a specific — and perfectly valid — PHP pattern makes an extract worker allocate memory without bound (observed 29 GB RSS on a single worker, ~100 GB system-wide including swap/compression before the memory watchdog SIGKILLs the process group). The run always dies with exit 137 shortly before extraction completes, because the affected files are the slowest and finish last.
A stack sample of the ballooning worker shows it spinning inside the trait flattener:
Root cause (from behavior)
Trait
useresolution appears to ignore namespaces and aliases and resolve by short name. When a class has the same short name as a trait it imports under an alias, the resolver picks the class itself as the trait to flatten, andflatten_trait_into_classrecurses/loops forever while copying fields.Minimal reproduction (2 files, 15 lines)
src/delivery-trait.php:src/delivery-class.php:Steps:
Result: the worker's RSS grows until the memory watchdog kills the process group; the CLI exits with code 137. Remove the alias (or rename the class so it no longer collides with the trait's short name) and the same repo indexes in milliseconds.
Note this is idiomatic PHP:
use Vendor\Traits\Delivery as Delivery_Trait;insideclass Deliveryis a common way to disambiguate, and PHP itself resolves it correctly.Environment
Deliveryimport a sharedtrait Deliveryfrom another namespace viause ... as Delivery_Trait;Impact / workaround
index_repositorytakes the whole MCP server down with it when the watchdog fires.CBM_WORKERS=1,CBM_DISABLE_LSP_CROSS=1andCBM_LSP_DISABLED=1do not prevent it (tested)..cbmignore.Suggested fixes
usestatements through the file's import table (aliases + namespaces) instead of short names, and never resolve auseinside a class body to a class.flatten_trait_into_classso any resolution cycle degrades gracefully instead of allocating until the watchdog kills the run.May relate to the memory reports in #581 / #593 / #832, but this one is a deterministic infinite loop with a tiny repro rather than gradual growth/retention.