abhigyanpatwari/GitNexus · error · RegistryAmbiguousTargetError
Multiple registered repos match "${target}": ${listing} Pass
Error message
Multiple registered repos match "${target}":
${listing}
Pass the absolute path instead to disambiguate. What it means
RegistryAmbiguousTargetError: the target matched multiple handles by name (nameMatches.length > 1) and cwd-based disambiguation (pickRepoHandleForCwd) could not pick one (#1658). The message renders a numbered listing of candidates and instructs the caller to pass the absolute path — needed when sibling checkouts share a directory name, since the first duplicate keeps id === name.
Source
Thrown at gitnexus/src/mcp/local/local-backend.ts:1907
// Path-like params first (absolute or contains separators) — aligns with
// resolveRegistryEntry (#829). Bare aliases such as ".tmp-repro-mini" must
// not be resolved via path.resolve(cwd) before duplicate-name handling.
if (looksLikePath) {
const pathMatch = resolvePathMatch();
if (pathMatch) return pathMatch;
}
// Exact name before id — the first duplicate sibling keeps id === name
// (e.g. id "shared"), so a name lookup must not be captured by the id tier.
const nameMatches = [...this.repos.values()].filter(
(handle) => handle.name.toLowerCase() === paramLower,
);
if (nameMatches.length === 1) return nameMatches[0];
if (nameMatches.length > 1) {
const cwdPick = this.pickRepoHandleForCwd(nameMatches);
if (cwdPick) return cwdPick;
throw new RegistryAmbiguousTargetError(
repoParam,
nameMatches.map((h) => this.handleToRegistryEntry(h)),
);
}
// Stable hashed id (e.g. "shared-abc123") from repoId() collision suffix
if (this.repos.has(paramLower)) return this.repos.get(paramLower)!;
// Bare name resolved as a cwd-relative path (e.g. "myrepo" against process.cwd()),
// after name/id tiers. Path-like strings with separators were handled at the top.
if (!looksLikePath) {
const pathMatch = resolvePathMatch();
if (pathMatch) return pathMatch;
}
// Partial name — only when unambiguous
const partialMatches = [...this.repos.values()].filter((handle) =>
handle.name.toLowerCase().includes(paramLower),View on GitHub (pinned to aac7515d2a)
Solutions
- Pass the absolute repository path as repo — the listing in the error shows each candidate's path.
- Or run the client from inside the intended checkout so cwd disambiguation resolves it automatically.
- Remove the stale duplicate from the registry (gitnexus clean / re-analyze from the correct path) if one entry is obsolete.
- Prefer ids only when unique — with duplicate names, name and id tiers cannot be trusted to pick the right sibling.
Example fix
# before: ambiguous short name
{"tool": "query", "args": {"search_query": "retry", "repo": "shared"}}
# → Multiple registered repos match "shared": ... Pass the absolute path instead to disambiguate.
# after: absolute path of the intended checkout
{"tool": "query", "args": {"search_query": "retry", "repo": "/srv/checkouts/a/shared"}} Defensive patterns
Strategy: validation
Validate before calling
// Resolve ambiguous names to absolute paths before any tool call
const { repositories } = await client.callTool({ name: 'list_repos', arguments: {} });
const byName = new Map<string, string[]>();
for (const r of repositories) byName.set(r.name.toLowerCase(), [...(byName.get(r.name.toLowerCase()) ?? []), r.repo_path ?? r.path]);
function resolveRepo(param: string, cwd: string): string {
const matches = byName.get(param.toLowerCase()) ?? [];
if (matches.length === 1) return matches[0];
const cwdMatch = matches.find((p) => cwd.startsWith(p));
if (cwdMatch) return cwdMatch;
if (matches.length > 1) throw new Error(`Ambiguous repo "${param}" — candidates: ${matches.join(', ')}`);
return param; // already a unique path/id
} Try / catch
try {
return await client.callTool({ name: 'query', arguments: { ...args, repo } });
} catch (err) {
if (err instanceof Error && err.message.includes('Multiple registered repos match')) {
// the error listing names each candidate's path — re-prompt or re-resolve from the registry
const paths = [...err.message.matchAll(/\/(\S+)/g)].map((m) => '/' + m[1]);
throw new UserChoiceError(`Pick one: ${paths.join('\n')}`);
}
throw err;
} Prevention
- Store absolute repo paths, not bare names, in client configs and agent instructions.
- Run clients from inside the intended checkout so cwd disambiguation applies.
- Avoid registering sibling clones that share a basename, or register them with distinct ids.
- Treat name collisions as a registry hygiene issue: clean stale entries with gitnexus clean.
When it happens
Trigger: Two or more registered repos share a basename (e.g. checkouts of the same repo under different worktrees/monorepo slots both named 'shared') and the caller passes repo: "shared" from a directory not inside either checkout.
Common situations: Multi-worktree setups; CI machines caching several clones of one repo at different paths; agents referencing repos by short name from a neutral cwd; registries accumulated over time from multiple clone locations.
Related errors
- Multiple repositories indexed. Specify which one with the "r
- Repository "${repoParam}" not found. Available: ${labels.joi
- No indexed repositories. Run: gitnexus analyze
- Multiple registered repos match "${target}": ${listing} Pass
- Missing Content-Length header from MCP client
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/2f3ead77f2eb69a0.
Report an issue: GitHub.