Summary
On a large Go monorepo (~4,260 .go files, 93k nodes, 256k edges, single language), codegraph_callers returns 0–2 callers for cross-package functions that grep finds hundreds to thousands of call sites for. In-package / unqualified call resolution works correctly.
This affects the central value proposition of the tool for Go projects — callers / impact / callees / trace all build on the same call edges. For a layered Go codebase (handler/ → service/ → domain/ → dao/ packages), where the vast majority of calls cross package boundaries, these tools effectively return empty results.
Environment
@colbymchenry/codegraph@0.9.4 (npm-shim, darwin-arm64)
- macOS 24.6.0
- Project: Go monolith, 4,260
.go files (+4 yaml), single Go module
- Index status:
- Files indexed: 4,264
- Nodes: 93,703
- Edges: 255,667
- DB size: 177.47 MB
- Journal mode: WAL
- Index is fresh (just ran
codegraph init -i), no concurrent writes during query
Observed Behavior — Recall Numbers
All symbol names below are anonymized; pkgA is a high-fan-in utility package (logging/error helpers), pkgB is a configuration/rights package. The shapes are real, only names are scrubbed.
| Symbol |
Call form in source |
Actual call sites (grep -rn) |
codegraph_callers returned |
Recall |
pkgA.FuncX (e.g. error helper) |
pkgA.FuncX(ctx, ...) (qualified, cross-package) |
5,303 |
1 |
~0.02% |
pkgA.FuncY (e.g. info helper) |
pkgA.FuncY(ctx, ...) (qualified, cross-package) |
603+ files |
2 |
<0.5% |
pkgB.FuncM |
qualified cross-package |
2 |
0 |
0% |
pkgB.FuncN |
qualified cross-package |
4 |
0 |
0% |
ok (in-package helper, multiple defs) |
bare call, same file |
n/a (aggregated query) |
3 returned + 10-symbol aggregation note ✅ |
works |
Querying with or without the package prefix in the symbol argument both fail equivalently:
codegraph_callers symbol="FuncY" → No callers found
codegraph_callers symbol="pkgA.FuncY" → 2 callers (vs 600+ actual)
codegraph_callers symbol="pkgB.FuncM" → No callers found
What Works vs What Doesn't
| Tool |
Verdict |
codegraph_search (find symbol by name) |
✅ works, fast |
codegraph_node (signature/source) |
✅ works |
codegraph_context (related symbols) |
🟡 partial — relies on edges, "related symbols" list quality degrades |
codegraph_files |
✅ works |
codegraph_callers |
❌ <1% recall on cross-pkg |
codegraph_callees |
❌ same root cause |
codegraph_impact |
❌ same root cause |
codegraph_trace |
❌ same root cause |
So definition-lookup is solid; the call-graph layer — the headline feature — is unusable on this codebase.
Reproduction Pattern
Any Go project with this shape will likely repro:
project/
pkg_a/
foo.go // package pkg_a; func Foo() {}
pkg_b/
bar.go // package pkg_b; import ".../pkg_a"; func Bar() { pkg_a.Foo() }
pkg_c/
baz.go // package pkg_c; import ".../pkg_a"; func Baz() { pkg_a.Foo() }
Expected: codegraph_callers Foo (or pkg_a.Foo) returns 2 callers.
Observed (on a 4k-file repo at least): 0 or 1, not 2.
I haven't built a minimal repro repo yet — happy to do so if it would help. The pattern above plus several hundred unrelated packages may be needed to trigger whatever the actual failure mode is (a tiny 3-file repro may resolve fine).
Hypothesis / Pseudocode
I haven't read src/resolution/import-resolver.ts end-to-end, but from #314's description ("import-resolver.ts handles import parsing for TypeScript/JavaScript, Python, Go, and PHP"), the Go branch exists. So this isn't "Go has no resolver" — it's "the Go resolver / edge writer drops the vast majority of cross-package edges silently."
Possible failure modes (pseudocode):
// In Go resolution, for a call expression `pkg.Func(args)`:
function resolveGoQualifiedCall(callExpr, fileImports) {
const pkgAlias = callExpr.qualifier // e.g. "pkgA"
const funcName = callExpr.name // e.g. "FuncX"
// Failure mode candidates:
// (A) pkg alias not found in import mapping because Go's
// import resolver only handles imports declared *without* alias,
// or only handles `import "path"` not `import name "path"`.
const importPath = fileImports[pkgAlias]
if (!importPath) return null // <- silently drops the edge
// (B) importPath resolved but findExportedSymbol() can't locate
// the target node because the function's qualified_name in the
// DB is stored as bare `FuncX`, not `pkgA.FuncX`, and the
// lookup uses the qualified form
const targetNode = findExportedSymbol(importPath, funcName)
if (!targetNode) return null // <- silently drops
// (C) Edge is inserted but writes are batched and some batches
// are lost on indexer shutdown / SQLite contention (less likely
// given consistency of ~0% recall, not random partial recall)
insertCallEdge(callerNode, targetNode)
}
(A) or (B) feels most likely given the cleanliness of the failure (~0 returned, not "some returned, some not"). A small fraction do get edges — possibly intra-module same-directory paths the path-proximity heuristic in findBestMatch (referenced in #314) happens to resolve.
Asks
- Can the maintainer confirm whether the Go resolver currently relies on path-proximity matching (
findBestMatch) rather than Go import declarations, or if there's an explicit extractGoImports branch?
- If the latter, is there a known limitation around module-qualified imports (
import alias "path", dot imports, blank imports, vendored deps) that drops the edge?
- Happy to run targeted queries against my local index (
.codegraph/codegraph.db SQLite) to validate hypotheses if useful — e.g., dump edges table grouped by source/target package to confirm whether cross-package edges exist at all.
Related Issues
Summary
On a large Go monorepo (~4,260
.gofiles, 93k nodes, 256k edges, single language),codegraph_callersreturns 0–2 callers for cross-package functions that grep finds hundreds to thousands of call sites for. In-package / unqualified call resolution works correctly.This affects the central value proposition of the tool for Go projects —
callers/impact/callees/traceall build on the same call edges. For a layered Go codebase (handler/→service/→domain/→dao/packages), where the vast majority of calls cross package boundaries, these tools effectively return empty results.Environment
@colbymchenry/codegraph@0.9.4(npm-shim, darwin-arm64).gofiles (+4 yaml), single Go modulecodegraph init -i), no concurrent writes during queryObserved Behavior — Recall Numbers
All symbol names below are anonymized;
pkgAis a high-fan-in utility package (logging/error helpers),pkgBis a configuration/rights package. The shapes are real, only names are scrubbed.grep -rn)codegraph_callersreturnedpkgA.FuncX(e.g. error helper)pkgA.FuncX(ctx, ...)(qualified, cross-package)pkgA.FuncY(e.g. info helper)pkgA.FuncY(ctx, ...)(qualified, cross-package)pkgB.FuncMpkgB.FuncNok(in-package helper, multiple defs)Querying with or without the package prefix in the
symbolargument both fail equivalently:codegraph_callers symbol="FuncY"→No callers foundcodegraph_callers symbol="pkgA.FuncY"→ 2 callers (vs 600+ actual)codegraph_callers symbol="pkgB.FuncM"→No callers foundWhat Works vs What Doesn't
codegraph_search(find symbol by name)codegraph_node(signature/source)codegraph_context(related symbols)codegraph_filescodegraph_callerscodegraph_calleescodegraph_impactcodegraph_traceSo definition-lookup is solid; the call-graph layer — the headline feature — is unusable on this codebase.
Reproduction Pattern
Any Go project with this shape will likely repro:
Expected:
codegraph_callers Foo(orpkg_a.Foo) returns 2 callers.Observed (on a 4k-file repo at least): 0 or 1, not 2.
I haven't built a minimal repro repo yet — happy to do so if it would help. The pattern above plus several hundred unrelated packages may be needed to trigger whatever the actual failure mode is (a tiny 3-file repro may resolve fine).
Hypothesis / Pseudocode
I haven't read
src/resolution/import-resolver.tsend-to-end, but from #314's description ("import-resolver.ts handles import parsing for TypeScript/JavaScript, Python, Go, and PHP"), the Go branch exists. So this isn't "Go has no resolver" — it's "the Go resolver / edge writer drops the vast majority of cross-package edges silently."Possible failure modes (pseudocode):
(A) or (B) feels most likely given the cleanliness of the failure (~0 returned, not "some returned, some not"). A small fraction do get edges — possibly intra-module same-directory paths the path-proximity heuristic in
findBestMatch(referenced in #314) happens to resolve.Asks
findBestMatch) rather than Go import declarations, or if there's an explicitextractGoImportsbranch?import alias "path", dot imports, blank imports, vendored deps) that drops the edge?.codegraph/codegraph.dbSQLite) to validate hypotheses if useful — e.g., dumpedgestable grouped by source/target package to confirm whether cross-package edges exist at all.Related Issues
typealias members not used in method-call resolution → false cross-modulecallsedges via path-proximity #359 — TypeScript: false cross-module call edges via path proximityreferencesedges — type annotations on parameters, return types, and fields are never extracted #381 — C#: zeroreferencesedges due to missing extraction