{"record":{"id":"2f3ead77f2eb69a0","repo":"abhigyanpatwari/GitNexus","slug":"multiple-registered-repos-match-target-lis","errorCode":null,"errorMessage":"Multiple registered repos match \"${target}\":\n${listing}\nPass the absolute path instead to disambiguate.","messagePattern":"Multiple registered repos match \"(.+?)\":\n(.+?)\nPass the absolute path instead to disambiguate\\.","errorType":"exception","errorClass":"RegistryAmbiguousTargetError","httpStatus":null,"severity":"error","filePath":"gitnexus/src/mcp/local/local-backend.ts","lineNumber":1907,"sourceCode":"\n      // Path-like params first (absolute or contains separators) — aligns with\n      // resolveRegistryEntry (#829). Bare aliases such as \".tmp-repro-mini\" must\n      // not be resolved via path.resolve(cwd) before duplicate-name handling.\n      if (looksLikePath) {\n        const pathMatch = resolvePathMatch();\n        if (pathMatch) return pathMatch;\n      }\n\n      // Exact name before id — the first duplicate sibling keeps id === name\n      // (e.g. id \"shared\"), so a name lookup must not be captured by the id tier.\n      const nameMatches = [...this.repos.values()].filter(\n        (handle) => handle.name.toLowerCase() === paramLower,\n      );\n      if (nameMatches.length === 1) return nameMatches[0];\n      if (nameMatches.length > 1) {\n        const cwdPick = this.pickRepoHandleForCwd(nameMatches);\n        if (cwdPick) return cwdPick;\n        throw new RegistryAmbiguousTargetError(\n          repoParam,\n          nameMatches.map((h) => this.handleToRegistryEntry(h)),\n        );\n      }\n\n      // Stable hashed id (e.g. \"shared-abc123\") from repoId() collision suffix\n      if (this.repos.has(paramLower)) return this.repos.get(paramLower)!;\n\n      // Bare name resolved as a cwd-relative path (e.g. \"myrepo\" against process.cwd()),\n      // after name/id tiers. Path-like strings with separators were handled at the top.\n      if (!looksLikePath) {\n        const pathMatch = resolvePathMatch();\n        if (pathMatch) return pathMatch;\n      }\n\n      // Partial name — only when unambiguous\n      const partialMatches = [...this.repos.values()].filter((handle) =>\n        handle.name.toLowerCase().includes(paramLower),","sourceCodeStart":1889,"sourceCodeEnd":1925,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/mcp/local/local-backend.ts#L1889-L1925","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: ambiguous short name\n{\"tool\": \"query\", \"args\": {\"search_query\": \"retry\", \"repo\": \"shared\"}}\n# → Multiple registered repos match \"shared\": ... Pass the absolute path instead to disambiguate.\n\n# after: absolute path of the intended checkout\n{\"tool\": \"query\", \"args\": {\"search_query\": \"retry\", \"repo\": \"/srv/checkouts/a/shared\"}}","handlingStrategy":"validation","validationCode":"// Resolve ambiguous names to absolute paths before any tool call\nconst { repositories } = await client.callTool({ name: 'list_repos', arguments: {} });\nconst byName = new Map<string, string[]>();\nfor (const r of repositories) byName.set(r.name.toLowerCase(), [...(byName.get(r.name.toLowerCase()) ?? []), r.repo_path ?? r.path]);\n\nfunction resolveRepo(param: string, cwd: string): string {\n  const matches = byName.get(param.toLowerCase()) ?? [];\n  if (matches.length === 1) return matches[0];\n  const cwdMatch = matches.find((p) => cwd.startsWith(p));\n  if (cwdMatch) return cwdMatch;\n  if (matches.length > 1) throw new Error(`Ambiguous repo \"${param}\" — candidates: ${matches.join(', ')}`);\n  return param; // already a unique path/id\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await client.callTool({ name: 'query', arguments: { ...args, repo } });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('Multiple registered repos match')) {\n    // the error listing names each candidate's path — re-prompt or re-resolve from the registry\n    const paths = [...err.message.matchAll(/\\/(\\S+)/g)].map((m) => '/' + m[1]);\n    throw new UserChoiceError(`Pick one: ${paths.join('\\n')}`);\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["mcp","repo-resolution","ambiguity","registry","worktrees"],"backgroundTag":"ambiguous-repository-selection","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}