Skip to content

Dead-code detection flags live code: constructors, dunders, and callback-by-reference all report "0 callers" #1642

Open

Description

@adinballew

Summary

The graph UI's dead-code classification (status: "dead" = "0 callers") flags a large number of live functions/methods as dead. The detector only counts direct CALLS/USAGE/CALL_REFERENCE edges and cannot see three common dispatch patterns, so it reports false positives for:

  1. Constructors — ClassName(...) instantiation never creates a CALLS edge to __init__.
  2. Dunder / framework-invoked methods — __str__, __call__, _generate/_agenerate (called by a framework's invoke/ainvoke), _llm_type (property access).
  3. Callback-by-reference — functions passed by name as arguments (asyncio.to_thread(fn, ...), context.configure(include_object=fn), DecisionPoint(apply=fn)).

Repro

On a Python repo, index it and open the graph UI. In my case (a Python monorepo), 50 of 50 "dead" nodes were false positives. Concrete example:

ModeAwareModel.__init__ is flagged dead, but it is instantiated in three places:

# ai/langgraph/services/model.py
def get_model() -> BaseChatModel:
    return ModeAwareModel(ChatOpenAI(...), role="supervisor")

def get_worker_model(session_id=None) -> BaseChatModel:
    return ModeAwareModel(ChatOpenAI(...), role="worker")

# inside bind_tools()
return ModeAwareModel(cast(BaseChatModel, bound), self._role)

The graph has no CALLS edge from ModeAwareModel(...) to ModeAwareModel.__init__, so the constructor is reported as having zero callers.

Other flagged-but-live examples:

  • _apply_updates — passed as apply=_apply_updates to a DecisionPoint.
  • _include_object / _compare_type — passed as include_object= / compare_type= to Alembic's context.configure.
  • _run_file_search — passed to asyncio.to_thread.
  • __str__, __call__, _generate, _agenerate, _llm_type — invoked by runtime/framework dispatch.

Root cause

The classification in src/ui/layout3d.c (cbm_layout_compute):

bool is_fn = label=="Function" || label=="Method";   // only these are candidates
int ic = in_calls[i];
int iu = in_usage[i] + in_call_reference[i];
...
else if (ic==0 && iu==0) status = "dead";

dead is defined purely as zero incoming CALLS/USAGE/CALL_REFERENCE edges. Static analysis cannot resolve constructor dispatch, dunder/framework invocation, or callback-by-reference, so all three produce false "dead" results.

Suggested fix

In the dead-code classification, treat these as live (non-dead):

  • __init__ — live when its owning class has any instantiation (Class(...) call) or is itself referenced/exported.
  • Dunder methods (__str__, __call__, __repr__, __eq__, etc.) — either treat as live, or exclude from dead-code classification entirely.
  • Callback-referenced functions — a function referenced by name as an argument (e.g. asyncio.to_thread(fn), configure(include_object=fn)) should count as having a caller.

At minimum, excluding __init__ and dunders from the "dead" bucket would eliminate the majority of false positives.

Environment

  • codebase-memory-mcp v0.10.4 (single static binary, --ui=true)
  • Python repo, ~13.4k graph nodes

Activity

  1. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Aug 18, 2026
  2. DeusData commented on Aug 18, 2026

    @DeusData
    Owner

    Thank you for the concrete live examples. The current UI classifier does exactly what you found: after excluding tests, entries, routes, and exports, any Function or Method with zero inbound CALLS, USAGE, and CALL_REFERENCE edges is labeled dead. That absence is not proof of dead code when implicit dispatch and callback references are outside the graph.

    This is routed as a high-priority correctness and UX bug. The first fix should remove the false certainty at the presentation boundary, for example by describing these as having no known inbound references, while constructor, dunder, framework, and callback extraction improvements remain separate graph-quality work. Thank you for showing that all 50 sampled results were false positives rather than reporting only one anecdote.

  3. bhaveshpatelapple commented on Sep 14, 2026

    @bhaveshpatelapple

    Confirming this is still present on v0.10.8 (b4b403b1...336a6, Windows amd64), independent Python repo (~10k nodes / ~40k edges).

    Minimal repro:

    class Embedder:
        def __init__(self, model="x"):
            self.model = model
    
    def build():
        return Embedder(model="y")   # real call site

    Embedder.__init__ is flagged status: "dead" in the graph UI with 0 callers, despite the real Embedder(model="y") call site in build(). Confirmed via trace_path(function_name="Embedder.__init__", direction="inbound") → callers_total: 0, and via a direct (non-OPTIONAL) Cypher MATCH (caller)-[:CALLS]->(f:Method {name:"__init__"}) → 0 rows, so it's not just a UI-layer miscount — the underlying CALLS edge for ClassName(...) → __init__ genuinely never gets created.

    On a real ~1,700-Function/136-Method project I indexed, this pattern accounted for 10 of 13 total flagged "dead" nodes — same shape as the ModeAwareModel example above.

  4. added a commit that references this issue on Sep 16, 2026
    43620d3
  5. DeusData commented on Sep 25, 2026

    @DeusData
    Owner

    Thank you, @adinballew, for sampling 50 results and showing that all 50 were live, and @bhaveshpatelapple for the minimal repro on v0.10.8!

    I confirmed it on current main. Embedder(model="y") does create a CALLS edge, but the edge points at the class Embedder, not at Embedder.__init__. So trace_path and the dead-code view both see zero callers for the constructor. That part is graph quality, and @mvanhorn has offered to take it. @mvanhorn, over to you, and thank you! Dunder methods and callbacks passed by reference are separate cases we'll look at afterwards.

    On the UI side, the graph view is being replaced. "No known inbound references" (rather than "dead") is already in the requirements for the new one. Thanks again for the concrete examples!

  6. mvanhorn commented on Sep 29, 2026

    @mvanhorn
    Contributor

    @DeusData the constructor half is up in #2424. Embedder(model="y") now resolves to Embedder.__init__, with the issue's repro as a test. One thing to confirm there: for a class that defines __init__, the Class node no longer gets the instantiation CALLS edge. Dunders and callbacks by reference are untouched.

  7. DeusData commented on Oct 19, 2026

    @DeusData
    Owner

    Thank you for the focused constructor work in #2424 and for calling out the Class-node edge change explicitly. That semantic choice needs to be settled in the PR review. Dunder dispatch and callbacks remain separate from the constructor slice; this follow-up does not mark the broader report resolved.

  8. mvanhorn commented on Oct 25, 2026

    @mvanhorn
    Contributor

    Agreed, the Class-node edge semantics belong in the constructor PR review. Dunder dispatch and callbacks remain separate, so the constructor slice alone doesn't resolve the broader report.

  9. DeusData commented on Oct 25, 2026

    @DeusData
    Owner

    Thank you for confirming the constructor scope. The Class-node edge semantics remain a review decision in #2424, while dunder dispatch and callbacks stay separately open. Sorry for the delay; this is waiting on maintainer follow-up, and I’ll follow up soon.

  10. DeusData commented on Nov 3, 2026

    @DeusData
    Owner

    Part of this landed on main in 8ae04f4 (#2424, thanks to @mvanhorn): a Python ClassName() call now resolves to the class's own __init__ when the class defines one, and keeps targeting the class node otherwise. Constructions reached only through module attributes or text resolvers still target the class, so this issue stays open for those.

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

    bugSomething isn't workingparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.ux/behaviorDisplay bugs, docs, adoption UX

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions