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

  1. Pass the absolute repository path as repo — the listing in the error shows each candidate's path.
  2. Or run the client from inside the intended checkout so cwd disambiguation resolves it automatically.
  3. Remove the stale duplicate from the registry (gitnexus clean / re-analyze from the correct path) if one entry is obsolete.
  4. 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

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


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/2f3ead77f2eb69a0. Report an issue: GitHub.