Skip to content

TypeScript: export const alias = fn breaks caller/impact traversal — callers/impact of the implementation omit every consumer calling through the alias (2-file repro) #1482

Description

@valkyriweb

Summary

When a function is re-exported under a value alias (export const alias = fn), CodeGraph indexes the call edge to the alias and the binding alias = fn, but never composes them. callers and impact on the implementation therefore omit every consumer that calls it through the alias — and report a complete-looking answer with a precise count and no uncertainty marker.

Verified on a 2-file, 6-node repro (below) and reproduced in a 16k-file TypeScript monorepo, where impact on a real function omitted both of its production call sites and listed only the definition file and its tests.

Minimal reproduction

Two files, fresh repo, nothing else:

impl.ts

export function realImpl(): string {
  return "real";
}

export const aliasName = realImpl;

consumer.ts

import { aliasName } from "./impl.js";

export function consumerFn(): string {
  return aliasName();
}
git init . && git add -A && git commit -m repro
codegraph init .          # 6 nodes, 8 edges

Actual

$ codegraph callers realImpl

Callers of "realImpl" (1):

file        impl.ts
  impl.ts:1

$ codegraph impact realImpl

Impact of changing "realImpl" — 2 affected symbols:

impl.ts
  function    realImpl:1
  file        impl.ts:1

consumer.ts does not appear in either. Changing realImpl breaks consumerFn, and impact analysis reports the blast radius as the definition file alone.

The control query shows the missing half of the join is present in the graph:

$ codegraph callers aliasName

Callers of "aliasName" (2):

function    consumerFn
  consumer.ts:3

file        consumer.ts
  consumer.ts:1

So the index contains consumerFn → aliasName and aliasName = realImpl. Only the composition is missing.

Expected

callers realImpl and impact realImpl should include consumerFn / consumer.ts, by resolving value aliases whose initializer is a direct reference to an indexed function. Failing that, the output should mark the result as potentially incomplete rather than presenting a count.

Why this is more than a missing edge

The failure is a false negative shaped like a complete answer. There is no "exact match not found" or "possible missing edges" signal, so a consumer cannot distinguish "nothing depends on this" from "the traversal stopped early".

That interacts badly with the MCP steering text. codegraph_explore's description instructs the agent to treat the returned source as already read and not re-open those files (src/mcp/tools.ts:682, src/mcp/server-instructions.ts:32,55), and no environment variable disables it. An agent asking "what breaks if I change this?" is told the blast radius is the definition file, and told not to run the verification that would catch the omission. For impact, which exists to answer exactly that question, silence is indistinguishable from safety.

Related

Same family as the per-language traversal gaps in #1187 (Java field injection), #1373 (C/C++ macros), and #899 (Python module attributes), but for TypeScript value-alias re-export. I could not find an existing report for this pattern.

Environment

  • CodeGraph 1.5.0, codegraph-darwin-arm64.tar.gz release bundle (bundled Node v24.16.0)
  • macOS arm64
  • DO_NOT_TRACK=1, CODEGRAPH_NO_UPDATE_CHECK=1
  • Also reproduced through serve --mcp with CODEGRAPH_CATCHUP_GATE_TIMEOUT_MS=0 (fully reconciled index, so not a staleness artifact)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions