Skip to content

fix(web): deduplicate repository lookups in search results - #1684

Open
dipeshbabu wants to merge 2 commits into
sourcebot-dev:mainfrom
dipeshbabu:dipeshbabu/fix-search-repository-lookups-1681
Open

dipeshbabu wants to merge 2 commits into
sourcebot-dev:mainfrom
dipeshbabu:dipeshbabu/fix-search-repository-lookups-1681

Conversation

@dipeshbabu

@dipeshbabu dipeshbabu commented Sep 24, 2026 •

Copy link
Copy Markdown

Fixes #1681

Search results containing many files from one repository started a database lookup for every file before the metadata cache was populated. Deduplicate repository identifiers before starting those lookups, so 100 files from one repository make one lookup and 100 files across two repositories make two.

Preserves legacy name-based lookup, missing-repository handling, result order, and the existing cache across streamed chunks. Regression tests cover each of these cases.

Validation completed before opening this PR:

The verification workflow lives on a separate branch in the fork.


Note

Low Risk
Localized search-path optimization with existing behavior preserved and new regression tests; no auth or data-model changes.

Overview
Fixes redundant Prisma repository lookups when Zoekt returns many file matches from the same repo(s). createReposMapForChunk now collects unique repository IDs (or legacy names) per chunk before querying, instead of starting a lookup per file.

Unary and streaming search still use the same cache, name-based fallback for shards without IDs, and behavior when a repo is missing (files omitted). CHANGELOG notes the fix; zoektSearcher.test.ts adds regression tests for 100-file deduplication, legacy name lookup, missing repos, and stream chunks reusing cached metadata.

Reviewed by Cursor Bugbot for commit cde93da. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes #1681 so search results with many files from the same repository no longer trigger a database lookup per file.

Deduplicates repository identifiers before resolving repository metadata in zoektSearcher.ts, so 100 files from one repository make one lookup and 100 files across two repositories make two. Legacy name-based lookups, missing-repository handling, result order, and the existing metadata cache across streamed chunks are preserved. Adds a changelog entry.

Tests

  • Adds regression coverage for repository ID deduplication, name-based lookups, missing repositories, and streaming chunks reusing cached metadata.

Written for commit cde93da. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Reduced duplicate repository metadata lookups when processing search result chunks.
    • Improved handling of legacy shards and search results with missing repository information.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8c1089ff-49c6-4942-9203-febc9f2fcb5e

📥 Commits

Reviewing files that changed from the base of the PR and between b493151 and cde93da.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • packages/web/src/features/search/zoektSearcher.test.ts
  • packages/web/src/features/search/zoektSearcher.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 6 remain after this review.


Walkthrough

The searcher now deduplicates repository IDs within each search result chunk before resolving repository metadata. Tests cover repeated IDs, legacy shards, missing repositories, and streaming chunks. The changelog records the fix.

Changes

Repository lookup deduplication

Layer / File(s) Summary
Deduplicate repository lookups per chunk
packages/web/src/features/search/zoektSearcher.ts, packages/web/src/features/search/zoektSearcher.test.ts, CHANGELOG.md
createReposMapForChunk processes unique repository IDs per chunk. Tests check repository lookup counts, file and repository metadata results, legacy shards, missing repositories, and streaming chunks. The changelog records the fix.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: brendan-kellam

Merge Risk: ⚪ Minimal · up to cde93

The lookup deduplication is ready to merge after normal checks; no actionable risk remains.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: deduplicating repository lookups in web search results.
Linked Issues check ✅ Passed The implementation for #1681 updates createReposMapForChunk in packages/web/src/features/search/zoektSearcher.ts to compute unique repository IDs before database lookups. This limits lookups to on…
Out of Scope Changes check ✅ Passed The changes stay within #1681. The production change fixes duplicate repository lookups. The tests verify the required behavior and compatibility cases. The changelog entry documents the fix. No unrel…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 3 files

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/web/src/features/search/zoektSearcher.test.ts">

<violation number="1" location="packages/web/src/features/search/zoektSearcher.test.ts:188">
P3: The streaming test verifies only file and repositoryInfo counts, never that each `response.files[i].repositoryId` corresponds to the emitted `repository_id`. The unary tests assert this mapping; the streaming chunk test should too, so a cache mis-association or reordering that keeps counts unchanged cannot slip through — it also directly covers the PR's "result order preserved" claim for the cross-chunk cache path.</violation>
</file>

<file name="packages/web/src/features/search/zoektSearcher.ts">

<violation number="1" location="packages/web/src/features/search/zoektSearcher.ts:344">
P3: Within a chunk the dedup is correct, but repositories that are not in the database never get cached (`if (repo) { reposMapCache.set(id, repo); }` only stores hits), so a missing repo referenced by a shard is still looked up once per streaming chunk instead of once per stream. Since this PR exists to collapse repeated lookups, track ids already queried in this stream (e.g., a `Set` of looked-up ids next to `_reposMapCache`) and skip re-querying them.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

grpcStream.emit('data', { response_chunk: { files: ids.map(id => createFile(id)) } });
const chunk = await reader.read();
const response = JSON.parse(new TextDecoder().decode(chunk.value).slice('data: '.length));
expect(response.files).toHaveLength(ids.length);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The streaming test verifies only file and repositoryInfo counts, never that each response.files[i].repositoryId corresponds to the emitted repository_id. The unary tests assert this mapping; the streaming chunk test should too, so a cache mis-association or reordering that keeps counts unchanged cannot slip through — it also directly covers the PR's "result order preserved" claim for the cross-chunk cache path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/src/features/search/zoektSearcher.test.ts, line 188:

<comment>The streaming test verifies only file and repositoryInfo counts, never that each `response.files[i].repositoryId` corresponds to the emitted `repository_id`. The unary tests assert this mapping; the streaming chunk test should too, so a cache mis-association or reordering that keeps counts unchanged cannot slip through — it also directly covers the PR's "result order preserved" claim for the cross-chunk cache path.</comment>

<file context>
@@ -104,4 +125,75 @@ describe('zoektSearch', () => {
+            grpcStream.emit('data', { response_chunk: { files: ids.map(id => createFile(id)) } });
+            const chunk = await reader.read();
+            const response = JSON.parse(new TextDecoder().decode(chunk.value).slice('data: '.length));
+            expect(response.files).toHaveLength(ids.length);
+            expect(response.repositoryInfo).toHaveLength(new Set(ids).size);
+        }
</file context>
Suggested change
expect(response.files).toHaveLength(ids.length);
expect(response.files).toHaveLength(ids.length);
expect(response.files.map(file => file.repositoryId)).toEqual(ids);

await Promise.all(chunk.files.map(async (file) => {
const id = getRepoIdForFile(file);

const repoIds = [...new Set(chunk.files.map(getRepoIdForFile))];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Within a chunk the dedup is correct, but repositories that are not in the database never get cached (if (repo) { reposMapCache.set(id, repo); } only stores hits), so a missing repo referenced by a shard is still looked up once per streaming chunk instead of once per stream. Since this PR exists to collapse repeated lookups, track ids already queried in this stream (e.g., a Set of looked-up ids next to _reposMapCache) and skip re-querying them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/web/src/features/search/zoektSearcher.ts, line 344:

<comment>Within a chunk the dedup is correct, but repositories that are not in the database never get cached (`if (repo) { reposMapCache.set(id, repo); }` only stores hits), so a missing repo referenced by a shard is still looked up once per streaming chunk instead of once per stream. Since this PR exists to collapse repeated lookups, track ids already queried in this stream (e.g., a `Set` of looked-up ids next to `_reposMapCache`) and skip re-querying them.</comment>

<file context>
@@ -341,9 +341,8 @@ const encodeSSEREsponseChunk = (response: object | string) => {
-    await Promise.all(chunk.files.map(async (file) => {
-        const id = getRepoIdForFile(file);
-
+    const repoIds = [...new Set(chunk.files.map(getRepoIdForFile))];
+    await Promise.all(repoIds.map(async (id) => {
         const repo = await (async () => {
</file context>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Search result hydration performs duplicate repository lookups within the same chunk

1 participant