{"record":{"id":"f44035bd152df418","repo":"chatboxai/chatbox","slug":"failed-to-search-knowledge-base-id-kbid-ple","errorCode":null,"errorMessage":"Failed to search knowledge base (id: ${kbId}). Please try again later.","messagePattern":"Failed to search knowledge base \\(id: (.+?)\\)\\. Please try again later\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/knowledge-base/file-loaders.ts","lineNumber":473,"sourceCode":"      }\n      return results.map((r) => ({\n        id: r.id,\n        score: r.score,\n        ...r.metadata,\n      }))\n    }\n  } catch (e) {\n    log.error(`[FILE] Failed to search: kbId=${kbId}, queryLength=${query.length}`, e)\n\n    sentry.withScope((scope) => {\n      scope.setTag('component', 'knowledge-base-file')\n      scope.setTag('operation', 'search_knowledge_base')\n      scope.setExtra('kbId', kbId)\n      scope.setExtra('queryLength', query.length)\n      sentry.captureException(e)\n    })\n\n    throw new Error(`Failed to search knowledge base (id: ${kbId}). Please try again later.`)\n  }\n}\n\n// Read chunks from vector store\nexport async function readChunks(kbId: number, chunks: { fileId: number; chunkIndex: number }[]) {\n  try {\n    log.debug(`[FILE] Reading chunks: kbId=${kbId}, chunks=${chunks.length}`)\n\n    if (!chunks || chunks.length === 0) {\n      return []\n    }\n\n    const indexName = `kb_${kbId}`\n    const results: any[] = []\n\n    // Use single SQL query to get all chunks at once\n    log.debug(`[FILE] Using single SQL query via vectorStore.turso for ${chunks.length} chunks`)\n","sourceCodeStart":455,"sourceCodeEnd":491,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/knowledge-base/file-loaders.ts#L455-L491","documentation":"Thrown by Gemini.listModels() (built-in Google Gemini provider) when GET `${geminiAPIHost}/v1beta/models?key=...` returns a body without a top-level `models` array. Unlike the custom variant, this method is NOT wrapped in a swallowing try/catch, so the ApiError propagates to the caller. Google's ListModels response is `{ models: [...] }`; anything else is an error envelope (auth/quota) or a misrouted response.","triggerScenarios":"Invalid or missing `geminiAPIKey` (Google returns `{ error: { code, message, status } }`); `geminiAPIHost` set to a value that already contains `/v1beta` (URL becomes `.../v1beta/v1beta/models`); the key is restricted to an API that does not include the Generative Language API; a proxy strips the `?key=` query string; region blocks the endpoint and a CDN returns an HTML/JSON error that parses without `models`.","commonSituations":"API key generated under a Google project where the Generative Language API is disabled; key with HTTP-referrer or IP restriction; user set a custom `geminiAPIHost` that includes a version path; corporate proxy returning a JSON error block; free-tier quota exceeded so the API returns an error object instead of the model list.","solutions":["Inspect the JSON.stringify'd payload — Google's `{ error: { code, message, status } }` tells you auth (`UNAUTHENTICATED`), quota (`RESOURCE_EXHAUSTED`), or permission denied.","Set `geminiAPIHost` to the base origin (e.g. `https://generativelanguage.googleapis.com`) — do not include `/v1beta`.","Enable the 'Generative Language API' in the Google Cloud project that owns the key and remove referrer/IP restrictions for desktop usage.","Wrap the listModels call in try/catch and fall back to a hardcoded model list so a transient listing failure does not block chat."],"exampleFix":"// before\nconst models = await gemini.listModels()  // propagates ApiError\n\n// after\ntry {\n  const models = await gemini.listModels()\n} catch (e) {\n  console.warn('Gemini listModels failed, using fallback', e)\n  return [{ modelId: 'gemini-2.5-flash', type: 'chat' }]\n}","handlingStrategy":"try-catch","validationCode":"async function probeGeminiModels(host: string, key: string): Promise<boolean> {\n  const res = await fetch(`${host}/v1beta/models?key=${encodeURIComponent(key)}`)\n  const json = await res.json().catch(() => ({}))\n  return Array.isArray((json as any)?.models)\n}","typeGuard":"function isGeminiModelsResponse(json: unknown): json is { models: { name: string; supportedGenerationMethods: string[]; inputTokenLimit: number; outputTokenLimit: number; displayName: string }[] } {\n  return typeof json === 'object' && json !== null && Array.isArray((json as any).models)\n}","tryCatchPattern":"try { return await gemini.listModels() }\ncatch (e) {\n  if (e instanceof ApiError) { console.warn('Gemini listModels body:', e.message); return [] }\n  throw e\n}","preventionTips":["Enable the Generative Language API on the Google project that owns the key.","Use base-origin geminiAPIHost (no /v1beta suffix).","Keep a fallback Gemini model list so listing failure does not block chat.","Avoid API-key IP/referrer restrictions for desktop/mobile use."],"tags":["api","gemini","models-list","configuration","auth"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}