Skip to content

CommonJS: missing module exports resolve to unrelated unimported class methods #27

Open

Description

@inth3shadows

Status: independently reproduced 2026-11-08 on em.40, pristine upstream main and upstream v1.6.2.
This is separate from the external-import/missing-dependency issue tracked in
#26. No resolver fix is included.

Minimal reproduction

// main.js
const utils = require('./utils');
function run() { return utils.missing(); }
module.exports = run;
// utils.js — a real local module, but it does not export missing
exports.known = function known() { return 1; };
// decoy.js — nothing imports or instantiates Decoy
export class Decoy { missing() { return 42; } }

From the directory containing these three files, set BUILD to the tested codegraph.js and run:

CODEGRAPH_KERNEL=0 node "$BUILD" init -y
CODEGRAPH_KERNEL=0 node "$BUILD" callees run -j

Fresh indexing correctly records main.js importing utils.js. It then fabricates:

run, main.js:2 --calls--> decoy.js:Decoy::missing
refName: utils.missing
resolvedBy: instance-method
provenance: null

Expected: missing stays unresolved because utils.js exports no such member. The decoy is neither
imported nor a runtime receiver of the call. Without decoy.js the reference is unresolved.
The extractor retains utils.missing, so the receiver is not lost at extraction.

This is an asserted wrong call edge, not merely an edge labelled as a heuristic. The graph now
attributes a caller to a method the source never invokes. No downstream impact-query output was
separately measured.

Independent verification

All subjects used Node22.22.2 on Linux/WSL and kernel0/WASM.

Subject Commit Semantic outcomes
Deployed em.40 5e06648 5 controls pass, missing-method counterexample fails
Official upstream main f72c9d2 Same six case/edge records as em.40
Official upstream v1.6.2 tag 6560052 Missing-method counterexample also fails; real-export control also fails

The already independently built upstream subjects were reused: official GitHub clones, fresh
locked dependencies, official npm run build, no fork source/node_modules/grammar reuse. Both
upstream source trees remain pristine. All18 fresh indexes for this investigation completed
successfully. JSON result files retain source identities and runtime component hashes.

The extra release-only real-export failure is important: do not claim that all six records
match across all versions. The current-main/em.40 missing-export defect is the remaining case.

Controls

Case em.40 / upstream main Upstream v1.6.2
Missing module member, no decoy Correctly unresolved Correctly unresolved
Same-name unimported class method Wrong Decoy::missing call Same wrong call
Same-name standalone exported function instead of a method Correctly unresolved Correctly unresolved
Real exports.missing function beside decoy Correctly calls utils.js:missing Wrongly calls decoy
Different-name class method Correctly unresolved Correctly unresolved
Equivalent import * as utils namespace syntax Correctly unresolved Correctly unresolved

No package installation or external dependency is involved in this minimal source. This
distinguishes it from the first verified external-package/dependency-metadata bug.

Source explanation in em.40 / current upstream main

  • src/resolution/import-resolver.ts:2194–2207 represents a plain require binding with
    isDefault:true and isNamespace:false.
  • The import resolver can resolve a real exported member; that succeeds in the current-main
    real-export control. When the member is absent, resolution continues without a target.
  • src/resolution/name-matcher.ts:25029–25037 rejects method guessing for imported module
    receivers only when isImportedModuleReceiver says it owns the receiver.
  • isImportedModuleReceiver at name-matcher.ts:25387–25392 accepts namespace bindings or
    out-of-repository imports. A default binding from a local relative require is neither.
  • The code reaches the global method-by-name strategy at name-matcher.ts:25152–25177.
    Its single-method path around25253–25280 can choose Decoy::missing without receiver evidence.

This explains the observed class-method-versus-function distinction and the successful ESM
namespace control. It is a fallback after a known module member lookup, not an absent import
mapping or dropped receiver.

A correction must distinguish a known CommonJS module whose member is absent from a genuinely
unknown object receiver. Preserve real exported members and legitimate declared default
class/object/instance APIs. Do not simply turn all CommonJS default bindings into ESM namespaces;
CommonJS can export a class, callable or instance as its stated default.

Related upstream work and novelty limits

The original decoy lead is in QA report PR27:
https://github.com/inth3shadows/codegraph-qa/pull/27, issue-2952 / decoy, where utils.etag and
utils.missing gained unrelated class-method targets. This reduction isolates the missing-member
part only; factory-produced callable export recovery was not investigated.

Status and reporting plan

Local fork tracking only; no engine edit, fix commit, deployment or official-upstream submission is included. Native mode, published release packages, other languages and real-project prevalence remain unverified.

Official upstream submission is planned for Tuesday, November 10, after final verification and duplicate/related-issue review. This is a remaining method-fallback variant of the broader CommonJS issue family, not a claim of entirely novel behavior.

Activity

  1. inth3shadows commented on Nov 11, 2026

    @inth3shadows
    OwnerAuthor

    Filed upstream as colbymchenry#3272 (2026-11-11), re-verified on upstream main f72c9d2. The fix (#28 + #29) stays on the fork for now: it adds typescript as a runtime dependency and costs about +38% indexing time on Express.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions