diff --git a/CHANGELOG.md b/CHANGELOG.md index 689718d36..8d1663411 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added per-step token cost tracking and estimated tool call token usage to Ask Sourcebot chat history. [#1353](https://github.com/sourcebot-dev/sourcebot/pull/1353) ### Fixed +- Fixed a crash when searching with `context:` referencing a search context that does not exist; it now returns a graceful error. [#1362](https://github.com/sourcebot-dev/sourcebot/pull/1362) - Send anonymous server-side PostHog events as personless so unauthenticated requests don't inflate person counts. [#1367](https://github.com/sourcebot-dev/sourcebot/pull/1367) ## [5.0.4] - 2026-06-18 diff --git a/packages/web/src/features/search/parser.test.ts b/packages/web/src/features/search/parser.test.ts new file mode 100644 index 000000000..69c4fcb47 --- /dev/null +++ b/packages/web/src/features/search/parser.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import type { PrismaClient } from '@sourcebot/db'; +import { parseQuerySyntaxIntoIR } from './parser'; +import { ServiceErrorException } from '@/lib/serviceError'; +import { ErrorCode } from '@/lib/errorCodes'; + +describe('parseQuerySyntaxIntoIR', () => { + it('throws a ServiceErrorException when a search context is not found', async () => { + const prisma = { + searchContext: { + findUnique: async () => null, + }, + } as unknown as PrismaClient; + + const promise = parseQuerySyntaxIntoIR({ + query: 'Helpers context:0', + options: {}, + prisma, + }); + + await expect(promise).rejects.toBeInstanceOf(ServiceErrorException); + await expect(promise).rejects.toMatchObject({ + serviceError: { errorCode: ErrorCode.SEARCH_CONTEXT_NOT_FOUND }, + }); + }); + + it('expands a search context into its repo set when found', async () => { + const prisma = { + searchContext: { + findUnique: async () => ({ + repos: [{ name: 'org/repo-a' }, { name: 'org/repo-b' }], + }), + }, + } as unknown as PrismaClient; + + const ir = await parseQuerySyntaxIntoIR({ + query: 'context:my-context', + options: {}, + prisma, + }); + + expect(JSON.stringify(ir)).toContain('org/repo-a'); + expect(JSON.stringify(ir)).toContain('org/repo-b'); + }); +}); diff --git a/packages/web/src/features/search/parser.ts b/packages/web/src/features/search/parser.ts index 3b188a825..bb3ee5d77 100644 --- a/packages/web/src/features/search/parser.ts +++ b/packages/web/src/features/search/parser.ts @@ -114,7 +114,11 @@ export const parseQuerySyntaxIntoIR = async ({ }); if (!context) { - throw new Error(`Search context "${contextName}" not found`); + throw new ServiceErrorException({ + statusCode: StatusCodes.NOT_FOUND, + errorCode: ErrorCode.SEARCH_CONTEXT_NOT_FOUND, + message: `Search context "${contextName}" not found`, + }); } return context.repos.map((repo) => repo.name);