{"record":{"id":"5154609733d17b4d","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-search-answer-sources","errorCode":"error-invalid-search-answer-sources","errorMessage":"error-invalid-search-answer-sources","messagePattern":"error-invalid-search-answer-sources","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/ai-search.ts","lineNumber":190,"sourceCode":"\nconst getSearchAnswerMessagesForUser = async (userId: string, messages: SearchAnswer['messages']) => {\n\tconst messageIds: string[] = [];\n\tconst messageIdSet = new Set<string>();\n\tconst scoreByMessageId = new Map<string, number | undefined>();\n\tconst clampScore = (score: number | undefined): number | undefined =>\n\t\ttypeof score === 'number' && Number.isFinite(score) ? Math.min(1, Math.max(0, score)) : undefined;\n\tfor (const { _id, score } of messages) {\n\t\tif (!_id) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!messageIdSet.has(_id)) {\n\t\t\tmessageIdSet.add(_id);\n\t\t\tmessageIds.push(_id);\n\t\t}\n\t\tscoreByMessageId.set(_id, clampScore(score));\n\t}\n\tif (!messageIds.length) {\n\t\tthrow new Meteor.Error('error-invalid-search-answer-sources');\n\t}\n\n\tconst docs = await Messages.findVisibleByIds(messageIds, {\n\t\tprojection: { _id: 1, rid: 1, msg: 1, ts: 1, u: 1 },\n\t}).toArray();\n\tconst subscribedRoomIds = await getSubscribedRoomIds(\n\t\tuserId,\n\t\tdocs.map((message) => message.rid),\n\t);\n\tif (docs.length !== messageIds.length || docs.some((message) => !subscribedRoomIds.has(message.rid))) {\n\t\tthrow new Meteor.Error('error-invalid-search-answer-sources');\n\t}\n\n\tconst normalizedDocs = await normalizeMessagesForUser(docs, userId);\n\tconst docsById = new Map(normalizedDocs.map((message) => [message._id, message]));\n\tconst rooms = await getRoomMap(normalizedDocs.map((message) => message.rid));\n\n\tconst answerMessages = [];","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/ai-search.ts#L172-L208","documentation":"getSearchAnswerMessagesForUser (apps/meteor/server/api/v1/ai-search.ts) builds the source list for the intelligent-search answer endpoint (POST searchAnswer-style route gated on license + intelligentSearchEnabled + answerGenerationConfigured). It collects message IDs from the request's messages array, skipping entries without an _id, deduplicating them. If not even one usable ID remains it throws Meteor error-invalid-search-answer-sources: the answer request carries no citable sources at all.","triggerScenarios":"POST to the AI search answer endpoint with messages: [] or with entries that all lack _id (e.g. [{score:0.8}]). The endpoint status checks pass, then this validation fails before any DB access.","commonSituations":"Client forwarding an empty sources array because the upstream search step returned no hits; schema drift where the field is named id/messageId instead of _id; testing the endpoint with placeholder payloads.","solutions":["Pass at least one entry with a non-empty _id: { \"messages\": [{ \"_id\": \"aobEdbYhXfu5hkeqG\", \"score\": 0.9 }] }","Take source IDs from the ai-search results endpoint rather than fabricating them","If the search step legitimately found nothing, skip the answer request — there is nothing to cite"],"exampleFix":"// before\nPOST /api/v1/ai-search/answer { \"messages\": [] } // -> error-invalid-search-answer-sources\n\n// after\nPOST /api/v1/ai-search/answer { \"messages\": [{ \"_id\": \"aobEdbYhXfu5hkeqG\", \"score\": 0.9 }] }","handlingStrategy":"validation","validationCode":"function assertAnswerSources(messages: Array<{ _id?: string; score?: number }>) {\n  const ids = messages.filter((m) => typeof m._id === 'string' && m._id.trim() !== '');\n  if (!ids.length) throw new Error('answer request needs >=1 message with a non-empty _id');\n  return ids;\n}","typeGuard":"const isSearchAnswerSource = (v: unknown): v is { _id: string; score?: number } =>\n  typeof v === 'object' && v !== null && typeof (v as any)._id === 'string' && (v as any)._id.length > 0;","tryCatchPattern":"try {\n  const { data } = await client.post('/api/v1/ai-search/answer', { messages });\n} catch (e: any) {\n  if (e?.response?.data?.errorType === 'error-invalid-search-answer-sources' && !messages.some((m) => m._id)) {\n    throw new ValidationError('no citable sources — run the search step first');\n  }\n  throw e;\n}","preventionTips":["Skip the answer call when the preceding search returned zero hits","Name the field _id exactly — id/messageId variants are dropped silently","Verify intelligent search + answer generation are configured before building answer UX"],"tags":["rest-api","ai-search","validation","meteor-error"],"backgroundTag":"invalid-request-payload","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T10:36:37.832Z"}