Skip to content

PHP: definitions pass still loops unbounded on trait-flattening name collisions in v0.9.0 — #951's repro still hangs, plus a new namespace-segment variant #1481

Closed

Description

@MoonFuji

Version

codebase-memory-mcp 0.9.0 (current latest release, published 2026-07-08)

Platform

Linux x86_64, 31 GB RAM

Install channel

GitHub release archive (portable tarball, run directly — no installer)

Summary

Two things:

  1. Regression / verification failure. Indexer OOM-killed (exit 137): infinite loop in PHP trait flattening when a class shares its short name with an aliased trait import #951 was closed 2026-07-09 with "The fix itself shipped with fix(lsp/php): prevent OOM when a PHP trait uses itself #920's guards in v0.9.0". Running the v0.9.0 release binary, Indexer OOM-killed (exit 137): infinite loop in PHP trait flattening when a class shares its short name with an aliased trait import #951's exact shape still hangs with unbounded memory growth in the definitions pass.
  2. A new, distinct trigger shape. The collision does not have to be with the composed trait's own short name. It also fires when the class's short name equals an intermediate namespace segment in the composed trait's import path. This shape is not covered by PHP: trait whose name collides with an aliased use … as import it composes → unbounded recursion in definitions pass, OOM (SIGKILL) #765, Indexer OOM-killed (exit 137): infinite loop in PHP trait flattening when a class shares its short name with an aliased trait import #951, or fix(lsp/php): prevent OOM when a PHP trait uses itself #920, and it is very common in Laravel apps.

Both stop at the same place — the log's last line is always pass.start pass=definitions — and both are independent of CBM_LSP_DISABLED / CBM_DISABLE_LSP_CROSS.

Reproduction

Shape B — new: class name == a namespace segment of a composed trait (hangs)

<?php
namespace App\M;

use App\Behaviours\Address\SaveGeofenceAttributes;  // segment "Address" == class name below

class Address {
    use SaveGeofenceAttributes;
    public function m($x) {}
}
codebase-memory-mcp cli index_repository --repo-path /path/to/dir --name probe

App\Behaviours\Address\SaveGeofenceAttributes does not need to exist — an unresolved import is enough.

Ablations (each run under a 4 GB cgroup cap, 20 s timeout):

Variant Result
as above ❌ HANG
namespace segment renamed Address → Addr ✅ OK
trait imported but not composed (use SaveGeofenceAttributes; removed) ✅ OK
no method declared ✅ OK
method present with empty body {} ❌ HANG
composed fully-qualified inline (use \App\Behaviours\Address\SaveGeofenceAttributes;, no import statement) ✅ OK

So shape B needs: import statement + composition by short name + at least one method declaration. A method declaration is sufficient; the body may be empty.

Shape A — #951's shape, still hanging on v0.9.0

<?php
namespace App\L;

use App\Traits\Card as CardBase;

class Card {
    use CardBase;
    public function m($x) { $this->a = $x; }
}

❌ HANG on the v0.9.0 release binary.

Full shape-A matrix:

Variant Result
class Card + use \App\Traits\Card; inline + method with body ❌ HANG
same, class renamed to CardX ✅ OK
same, method body empty {} ❌ HANG
same, no method at all ✅ OK
same, composed trait short name differs (\App\Traits\Panel) ✅ OK
class Card + unqualified use Card; ❌ HANG
import without alias (use App\Traits\Card; + use Card;) ❌ HANG
aliased import (#951's exact shape, above) ❌ HANG

Note a correction to #765, which reported that an empty method body avoids the bug: for the class shape that is not true — an empty-bodied method declaration still hangs. Only removing the method entirely avoids it.

Logs

Identical for every hanging variant — the last line is always the start of definitions:

level=info msg=mem.init budget_mb=1024 source=CBM_MEM_BUDGET_MB
level=info msg=worker.watchdog.start
level=info msg=pipeline.discover files=1 elapsed_ms=0
level=info msg=pipeline.route path=full
level=info msg=pass.start pass=structure files=1
level=info msg=pass.done pass=structure nodes=3 edges=2
level=info msg=pass.timing pass=structure elapsed_ms=0
level=info msg=pipeline.mode mode=sequential files=1
level=info msg=pkgmap.scan_repo manifests=0
level=info msg=pkgmap.scan manifests_from_files=0 manifests_from_walk=0 entries=0
level=info msg=pass.start pass=definitions files=1
<no further output — memory climbs to budget_mb, watchdog SIGTERMs, exit 15>

With a larger/default budget the same run reaches OOM-kill territory instead of a watchdog SIGTERM, matching #765/#951's exit 137.

Which knobs do and don't help

Setting Effect
CBM_LSP_DISABLED=1 + CBM_DISABLE_LSP_CROSS=1 ❌ still hangs (confirms it is upstream of the LSP resolvers)
CBM_INDEX_SINGLE_THREAD=1 ❌ still hangs
CBM_LSP_MAX_WALK_DEPTH=2 ❌ still hangs
CBM_INDEX_SUPERVISOR=1 + CBM_INDEX_WORKER_TIMEOUT_S=15 + CBM_INDEX_MAX_RESTARTS=5 ❌ does not rescue the run — the memory watchdog SIGTERMs the process group before the supervisor can time out, quarantine the file, and restart. No quarantine file is written. It would be very useful if the watchdog handed off to the supervisor here instead of killing the group.
CBM_WALK_DEFS_MAX=100 ⚠️ avoids the hang, but is not a usable workaround — see below

CBM_WALK_DEFS_MAX has a sharp cliff: ≤200 avoids the hang, ≥300 hangs. But it also caps definitions per file, which destroys the graph. Indexing one 258-file directory:

  • CBM_WALK_DEFS_MAX=100, LSP on → 534 nodes / 806 edges
  • CBM_LSP_DISABLED=1, no cap → 2,979 nodes / 7,178 edges

A single model file in that directory legitimately has 374 definitions, so a cap low enough to dodge the loop silently truncates real files.

The only practical workaround remains .cbmignore, which requires knowing the trigger files in advance.

Real-world prevalence

In one Laravel 12 application (23,720 .php files scanned, excluding vendor/), 22 files trigger one of the two shapes: 14 shape A, 8 shape B. Because indexing is all-or-nothing, any one of them fails the entire repo index.

Shape B is the one I'd flag as high-impact, because the pattern is idiomatic Laravel rather than unusual: behaviour/concern traits are commonly namespaced per entity, so

namespace App\Models\Account;
use App\Behaviours\Address\SaveGeofenceAttributes;
class Address extends Model { use SaveGeofenceAttributes; /* ... */ }

is an ordinary file. In the app above, the shape-B hits are exactly the Eloquent models named Address, Contract, Route, User, Broadcast, Company, PriceModification. Nothing about them is exotic.

With those 22 files excluded via .cbmignore, the same repo indexes fine with LSP enabled — 53,794 nodes / 222,112 edges / skipped_count: 0 in under 20 s — so this really is a small set of poison files rather than a scale problem.

Suggested fix

Same as #951's suggestion 2, extended: resolve trait use through the file's import table (aliases and full namespace paths), never resolve a composed trait to a class, and never match on a partial path segment. Independently, add a visited-set/depth guard in the trait flattener so any resolution cycle degrades gracefully instead of allocating until the watchdog fires — that guard is what would have caught both shapes regardless of how the name resolution behaves.

Anything else?

I have a small Python detector that finds both shapes in a tree (parses namespace, imports, in-class use, and method presence). Happy to attach it or contribute it as a test-corpus generator if useful.

Activity

  1. added this to the 0.10.0-rc milestone on Aug 10, 2026
  2. added
    bugSomething isn't working
    parsing/qualityGraph extraction bugs, false positives, missing edges
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Aug 10, 2026
  3. DeusData commented on Aug 10, 2026

    @DeusData
    Owner

    Thank you for carrying the original reproduction forward and adding the namespace-segment variant. The current PHP guard covers the exact class-versus-canonical-trait self case, but it does not cover the collision shape described here. I have routed this as a high-priority parsing and stability bug in the immediate train.

  4. DeusData commented on Sep 26, 2026

    @DeusData
    Owner

    Thank you, @MoonFuji, for carrying #951's reproduction forward and for the ablation tables. They made this easy to re-check!

    I ran your probes on current main and on the v0.11.0 release (macOS arm64) with a 1 GB budget. Shape B (a class named after a namespace segment of the composed trait) and all four shape-A variants now finish well under a second. The shape-A variants were #951's aliased import, inline fully-qualified, unqualified use Card; and unaliased import. I haven't pinned down which change fixed it, so I don't want to claim more than that. Could you re-run the same probes on Linux x86_64 with v0.11.0 under your 4 GB cgroup cap? If they pass there too, we'll close this.

  5. added
    awaiting-reporterWaiting on the reporter for info/repro; stale bot will warn then close
    on Sep 26, 2026
  6. github-actions commented on Sep 26, 2026

    @github-actions

    Thanks for the report! To move this forward we need a bit more so we can reproduce it ourselves:

    • the codebase-memory-mcp --version you're on
    • the exact steps or command you ran
    • a public repo (or a small dummy snippet) that shows the problem — please don't paste proprietary code

    Once that's here we'll pick it straight back up. Heads-up: issues left awaiting-reporter are automatically closed after a few weeks of silence, but a comment reopens the door anytime.

  7. MoonFuji commented on Sep 30, 2026

    @MoonFuji
    Author

    Re-ran on Linux x86_64 with v0.11.0 (portable release binary, checksum verified). Same setup as the original report: 4 GB cgroup cap with MemorySwapMax=0, 20 s timeout, CBM_MEM_BUDGET_MB=1024.

    You can close this from my side. Thanks for re-checking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    awaiting-reporterWaiting on the reporter for info/repro; stale bot will warn then closebugSomething isn't workingeditor/integrationEditor compatibility and CLI integrationparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.stability/performanceServer crashes, OOM, hangs, high CPU/memory

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions