abhigyanpatwari/GitNexus · warning
interface-dispatch: members over the fan-out cap dropped imp
Error message
interface-dispatch: members over the fan-out cap dropped implementors (their CALLS edges were not emitted)
What it means
During scope-resolution emit, the interface-dispatch pass fans a call through an interface member out to CALLS edges for each of its implementors. When a member has more implementors than MAX_INTERFACE_DISPATCH_FANOUT (default 32, overridable via GITNEXUS_MAX_INTERFACE_DISPATCH_FANOUT), the overflow implementors get no CALLS edges at all. The pipeline warns (#2829 contract) so the coverage loss is visible, because impact() on those dropped implementations would otherwise silently under-report.
Source
Thrown at gitnexus/src/core/ingestion/scope-resolution/pipeline/run.ts:1054
recordResolutionOutcome,
calleeIdSink: calleeIdAccumulator,
// The pass's only source of positive EXTERNAL evidence for a dropped
// receiver (`console.log`, `fetch(...)`). Same hook, same spelling as
// the `emitFreeCallFallback` wiring below.
isBuiltInName: provider.languageProvider.isBuiltInName,
// What each heritage clause instantiated its base with, so the
// interface-dispatch fan-out can refuse an incompatible instantiation
// (#2912). Empty under `callableFlowOnly`, which emits no dispatch.
heritageTypeArguments,
},
);
const receiverExtras = receiverBound.emitted;
if (receiverBound.dispatchFanoutSkipped > 0) {
// Never drop dispatch coverage silently (#2829) — same contract as the
// property-dispatch cap below. An interface member over the cap loses real
// implementors, so `impact()` on those implementations under-reports; an
// operator has to be able to see WHICH member lost them.
logger.warn(
{
lang: provider.language,
dispatchFanoutSkipped: receiverBound.dispatchFanoutSkipped,
dispatchFanoutSkippedNames: receiverBound.dispatchFanoutSkippedNames,
fanoutCap: MAX_INTERFACE_DISPATCH_FANOUT,
},
'interface-dispatch: members over the fan-out cap dropped implementors (their CALLS edges were not emitted)',
);
}
const unresolvedReceiverExtras =
!callableFlowOnly && provider.emitUnresolvedReceiverEdges !== undefined
? provider.emitUnresolvedReceiverEdges(
graph,
indexes,
emitParsedFiles,
postHeritageNodeLookup,
handledSites,
readonlyModel,View on GitHub (pinned to 0d1aed942f)
Solutions
- Set GITNEXUS_MAX_INTERFACE_DISPATCH_FANOUT to a value above your widest interface (e.g. 128) and re-run analyze
- Read dispatchFanoutSkippedNames in the warn to find the exact member(s), then verify with impact() on the implementors after re-indexing
- If the wide interface is test-mock noise, exclude generated/mock paths from indexing so the cap is not consumed by uninformative targets
- Keep the override bounded — the fan-out is a per-call-site product, so huge caps multiply graph size without adding actionable information
Example fix
# before export GITNEXUS_MAX_INTERFACE_DISPATCH_FANOUT= # unset → default 32, warn fires npx gitnexus analyze # after export GITNEXUS_MAX_INTERFACE_DISPATCH_FANOUT=128 npx gitnexus analyze # warn disappears; CALLS to implementors emitted
Defensive patterns
Strategy: validation
Validate before calling
// Before trusting impact() results, assert the emit logs are cap-clean:
import { execSync } from 'node:child_process';
const out = execSync('npx gitnexus analyze 2>&1', { encoding: 'utf8' });
if (out.includes('interface-dispatch: members over the fan-out cap')) {
throw new Error('impact() would under-report: raise GITNEXUS_MAX_INTERFACE_DISPATCH_FANOUT');
} Prevention
- Set GITNEXUS_MAX_INTERFACE_DISPATCH_FANOUT above your widest interface's implementor count before large indexes
- Grep analyze output for 'dispatchFanoutSkippedNames' and resolve every named member
- Never treat an empty impact() caller list on an implementor as proof of dead code when this warn appeared in the same run
When it happens
Trigger: Running analyze (emit phase, not callableFlowOnly) on a repo where one interface member is implemented by more than 32 types (mock proliferation is the usual cause); the receiverBound result reports dispatchFanoutSkipped > 0 and the orchestrator logs the warn with language, skipped count, skipped member names (bounded to 20 samples), and the cap.
Common situations: Large TypeScript/Java codebases with heavy interface mocking in tests, DI containers registering many implementors, generated code merging dozens of classes under one interface, or someone lowering the fan-out cap env below the repo's real implementor count.
Related errors
- callable-value-flow: candidate set exceeded the cap; no part
- property-dispatch: keys over the fan-out cap were dropped (n
- ${name} must be a positive integer, got "${raw}"
- ${name} must be a positive integer <= ${max}, got "${raw}"
- ${name} must be a non-negative integer, got "${raw}"
AI-assisted analysis of abhigyanpatwari/GitNexus@0d1aed942f (2026-08-20).
Data as JSON: /api/errors/74057e2dffc7f5f0.
Report an issue: GitHub.