Area
gitnexus (CLI / core / indexing / MCP server)
Summary
The MCP trace tool throws a raw TypeError: Cannot read properties of undefined (reading 'toLowerCase') while every other per-repo tool (context, impact, query, route_map, check, explain, list_repos) works against the same indexed repository. The root cause is a missing null/undefined guard in isTestFilePath() that is called during BFS traversal when nodes lacking a filePath property (Community, Process, etc.) are encountered.
Context
I'm running the GitNexus MCP server from a Docker deployment (gitnexus image, version 1.6.10-rc.19) and driving it from a remote MCP client. While smoke-testing every per-repo tool against one indexed repository, trace is the only tool that fails — and it fails with a low-level JS TypeError wrapped in a structured error envelope rather than returning a meaningful, actionable error message.
The other tools all succeed against the same repo:
| Tool |
Result |
list_repos |
✅ returns repo info normally |
context |
✅ returns symbol context (impl, callers, methods) |
impact |
✅ upstream analysis works |
query |
✅ semantic search works |
trace |
❌ {status: "error", error: "Cannot read properties of undefined (reading 'toLowerCase')"} |
explain |
✅ returns (no taint layer, as expected without --pdg) |
route_map |
✅ returns route info |
check |
✅ detects import cycles |
Because trace is the only failing tool and the error is a bare TypeError rather than a meaningful error message, this is a missing null/undefined guard — the index itself is clearly serviceable (every other tool reads it fine).
Expected behavior
trace should either return a path result, a no_path / not_found / ambiguous structured envelope, or — if a repo/symbol can't be resolved — a clear, actionable error message. It should never surface a raw TypeError: Cannot read properties of undefined (reading 'toLowerCase') to the MCP client.
Actual behavior
trace fails with:
{
"status": "error",
"error": "Cannot read properties of undefined (reading 'toLowerCase')",
"from": { "name": "<symbolA>" },
"to": { "name": "<symbolB>" },
"suggestion": "The graph query failed — try gitnexus context <symbol> to see connections, or check if an interface bridges them."
}
The error is caught by trace()'s own try/catch and returned as a structured {status:'error'} envelope, but the error field contains a raw JS TypeError message — not an actionable description of what went wrong. This makes the error opaque to the MCP client and the user.
Steps to reproduce
Reproduced against the Docker-hosted server via remote MCP:
Result: {status: "error", error: "Cannot read properties of undefined (reading 'toLowerCase')"}
The TypeError occurs with any from/to symbol pair where BFS traversal reaches nodes that lack a filePath property.
Root cause (confirmed)
1. isTestFilePath() has no null/undefined guard on its filePath parameter
local-backend.ts:207-208 (identical in both rc.19 and rc.44):
export function isTestFilePath(filePath: string): boolean {
const p = filePath.toLowerCase().replace(/\\/g, '/');
return (
p.includes('.test.') || p.includes('.spec.') || ...
);
}
The TypeScript type annotation says string, but the function is called with a value that may be undefined at runtime (see point 2 below). Calling undefined.toLowerCase() throws exactly the observed TypeError: Cannot read properties of undefined (reading 'toLowerCase').
2. BFS row decoding can produce undefined filePath
In _traceImpl(), the BFS traversal decodes rows from LadybugDB:
local-backend.ts:4723 (rc.19: line 4663):
const filePath = (row.filePath ?? row[4]) as string;
If m.filePath is null/undefined in the DB (some node types — e.g. Community, Process — do not have a filePath property), both row.filePath and row[4] will be null/undefined. The ?? operator falls through for both, so filePath ends up as undefined. This filePath is then passed to isTestFilePath(filePath) at line 4744 (rc.19: 4682):
if (!includeTests && isTestFilePath(filePath)) continue;
3. Confirmed: a significant number of nodes in the DB have NULL filePath
Cypher query against the remote LadybugDB:
MATCH (m) WHERE m.filePath IS NULL RETURN count(m) AS cnt
Returns a large count — these are primarily Community and Process nodes, plus other node types that lack a filePath property. The BFS traversal reaches these nodes via CALLS/HAS_METHOD/MEMBER_OF edges, triggering the crash.
4. trace()'s try/catch catches the TypeError but returns an opaque error message
local-backend.ts:4534-4548:
private async trace(repo: RepoHandle, params: TraceParams): Promise<any> {
try {
return await this._traceImpl(repo, params);
} catch (err: any) {
return {
status: 'error',
error: (err instanceof Error ? err.message : String(err)) || 'Trace analysis failed',
// ...
};
}
}
The try/catch does catch the TypeError, but err.message is the raw JS error text "Cannot read properties of undefined (reading 'toLowerCase')", which is opaque and unactionable. The error is not logged to stdout/stderr (server.ts's top-level catch is not reached), so docker logs shows nothing.
5. Registry data is valid — the "bad registry entry" hypothesis is disproved
The remote server's registry.json contains exactly one valid entry with a valid name field. With only one valid repo, resolveRepoFromCache(undefined) returns directly via the single-repo branch (this.repos.size === 1) without hitting any unguarded .toLowerCase() calls in the repo-resolution path.
6. Other unguarded .toLowerCase() calls in repo-resolution are not triggered
The repo-resolution path (resolveRepo → resolveRepoFromCache) has several unguarded .toLowerCase() calls on handle.name and repoParam, but these are only reached when:
repoParam is provided (line 1278: repoParam.toLowerCase()), OR
- Multiple repos exist and name matching is needed (lines 1303, 1327:
handle.name.toLowerCase()), OR
- No repo match is found and the "Available: …" error list is built (lines 1136, 1140:
h.name.toLowerCase()).
None of these conditions apply when there is exactly one repo and repoParam is omitted.
Environment
- GitNexus version (deployed, remote Docker):
1.6.10-rc.19
- GitNexus source reviewed:
v1.6.10-rc.19 @ d43c479a and main @ 3c36ab90 (v1.6.10-rc.44)
- Deployment: Docker image, MCP server reached over HTTP (Streamable HTTP / SSE) from a remote MCP client
- OS (server): Linux (Docker container,
node:22-bookworm-slim)
- Node.js (server): v22.22.2
- Client: remote MCP client
Suggested fix
1. Add a null guard to isTestFilePath() — the confirmed crash point:
export function isTestFilePath(filePath: string | null | undefined): boolean {
if (!filePath || typeof filePath !== 'string') return false;
const p = filePath.toLowerCase().replace(/\\/g, '/');
// ...
}
2. Add a null guard to BFS row decoding for filePath:
const filePath = (row.filePath ?? row[4] ?? '') as string;
3. Add null guards to resolveRepo / resolveRepoFromCache for handle.name and repoParam — even though they are not the current crash point, they are defensive gaps that could cause similar TypeErrors under different conditions (multi-repo setups, malformed registry entries):
const paramLower = (repoParam ?? '').toLowerCase();
const handleNameLower = (handle.name ?? '').toLowerCase();
A defensive fix across all three locations would ensure that any undefined/null value produces a clear, actionable error or graceful fallback instead of a bare TypeError — and would make trace no longer uniquely fragile relative to its sibling tools.
Area
gitnexus (CLI / core / indexing / MCP server)
Summary
The MCP
tracetool throws a rawTypeError: Cannot read properties of undefined (reading 'toLowerCase')while every other per-repo tool (context,impact,query,route_map,check,explain,list_repos) works against the same indexed repository. The root cause is a missing null/undefined guard inisTestFilePath()that is called during BFS traversal when nodes lacking afilePathproperty (Community, Process, etc.) are encountered.Context
I'm running the GitNexus MCP server from a Docker deployment (
gitnexusimage, version1.6.10-rc.19) and driving it from a remote MCP client. While smoke-testing every per-repo tool against one indexed repository,traceis the only tool that fails — and it fails with a low-level JSTypeErrorwrapped in a structured error envelope rather than returning a meaningful, actionable error message.The other tools all succeed against the same repo:
list_reposcontextimpactquerytrace{status: "error", error: "Cannot read properties of undefined (reading 'toLowerCase')"}explain--pdg)route_mapcheckBecause
traceis the only failing tool and the error is a bareTypeErrorrather than a meaningful error message, this is a missing null/undefined guard — the index itself is clearly serviceable (every other tool reads it fine).Expected behavior
traceshould either return a path result, ano_path/not_found/ambiguousstructured envelope, or — if a repo/symbol can't be resolved — a clear, actionable error message. It should never surface a rawTypeError: Cannot read properties of undefined (reading 'toLowerCase')to the MCP client.Actual behavior
tracefails with:{ "status": "error", "error": "Cannot read properties of undefined (reading 'toLowerCase')", "from": { "name": "<symbolA>" }, "to": { "name": "<symbolB>" }, "suggestion": "The graph query failed — try gitnexus context <symbol> to see connections, or check if an interface bridges them." }The error is caught by
trace()'s own try/catch and returned as a structured{status:'error'}envelope, but theerrorfield contains a raw JSTypeErrormessage — not an actionable description of what went wrong. This makes the error opaque to the MCP client and the user.Steps to reproduce
Reproduced against the Docker-hosted server via remote MCP:
Result:
{status: "error", error: "Cannot read properties of undefined (reading 'toLowerCase')"}The TypeError occurs with any
from/tosymbol pair where BFS traversal reaches nodes that lack afilePathproperty.Root cause (confirmed)
1.
isTestFilePath()has no null/undefined guard on itsfilePathparameterlocal-backend.ts:207-208(identical in both rc.19 and rc.44):The TypeScript type annotation says
string, but the function is called with a value that may beundefinedat runtime (see point 2 below). Callingundefined.toLowerCase()throws exactly the observedTypeError: Cannot read properties of undefined (reading 'toLowerCase').2. BFS row decoding can produce
undefinedfilePathIn
_traceImpl(), the BFS traversal decodes rows from LadybugDB:local-backend.ts:4723(rc.19: line 4663):If
m.filePathis null/undefined in the DB (some node types — e.g.Community,Process— do not have afilePathproperty), bothrow.filePathandrow[4]will be null/undefined. The??operator falls through for both, sofilePathends up asundefined. ThisfilePathis then passed toisTestFilePath(filePath)at line 4744 (rc.19: 4682):3. Confirmed: a significant number of nodes in the DB have NULL filePath
Cypher query against the remote LadybugDB:
Returns a large count — these are primarily
CommunityandProcessnodes, plus other node types that lack afilePathproperty. The BFS traversal reaches these nodes via CALLS/HAS_METHOD/MEMBER_OF edges, triggering the crash.4.
trace()'s try/catch catches the TypeError but returns an opaque error messagelocal-backend.ts:4534-4548:The try/catch does catch the TypeError, but
err.messageis the raw JS error text"Cannot read properties of undefined (reading 'toLowerCase')", which is opaque and unactionable. The error is not logged to stdout/stderr (server.ts's top-level catch is not reached), sodocker logsshows nothing.5. Registry data is valid — the "bad registry entry" hypothesis is disproved
The remote server's
registry.jsoncontains exactly one valid entry with a validnamefield. With only one valid repo,resolveRepoFromCache(undefined)returns directly via the single-repo branch (this.repos.size === 1) without hitting any unguarded.toLowerCase()calls in the repo-resolution path.6. Other unguarded
.toLowerCase()calls in repo-resolution are not triggeredThe repo-resolution path (
resolveRepo→resolveRepoFromCache) has several unguarded.toLowerCase()calls onhandle.nameandrepoParam, but these are only reached when:repoParamis provided (line 1278:repoParam.toLowerCase()), ORhandle.name.toLowerCase()), ORh.name.toLowerCase()).None of these conditions apply when there is exactly one repo and
repoParamis omitted.Environment
1.6.10-rc.19v1.6.10-rc.19@d43c479aandmain@3c36ab90(v1.6.10-rc.44)node:22-bookworm-slim)Suggested fix
1. Add a null guard to
isTestFilePath()— the confirmed crash point:2. Add a null guard to BFS row decoding for
filePath:3. Add null guards to
resolveRepo/resolveRepoFromCacheforhandle.nameandrepoParam— even though they are not the current crash point, they are defensive gaps that could cause similar TypeErrors under different conditions (multi-repo setups, malformed registry entries):A defensive fix across all three locations would ensure that any undefined/null value produces a clear, actionable error or graceful fallback instead of a bare
TypeError— and would maketraceno longer uniquely fragile relative to its sibling tools.