{"record":{"id":"6ca6d1fe6df001cc","repo":"can1357/oh-my-pi","slug":"err-message","errorCode":null,"errorMessage":"${err.message}","messagePattern":"\\$\\{err\\.message\\}","errorType":"exception","errorClass":"SearchProviderError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/web/search/providers/kagi.ts","lineNumber":68,"sourceCode":"\t\t\t\tfetch: params.fetch,\n\t\t\t},\n\t\t\tparams.authStorage,\n\t\t);\n\n\t\treturn {\n\t\t\tprovider: \"kagi\",\n\t\t\tsources: toSearchSources(result.sources, numResults),\n\t\t\trelatedQuestions: result.relatedQuestions.length > 0 ? result.relatedQuestions : undefined,\n\t\t\trequestId: result.requestId,\n\t\t\tanswer: result.answer,\n\t\t};\n\t} catch (err) {\n\t\tif (err instanceof KagiApiError) {\n\t\t\tif (typeof err.statusCode === \"number\") {\n\t\t\t\tconst classified = classifyProviderHttpError(\"kagi\", err.statusCode, err.message);\n\t\t\t\tif (classified) throw classified;\n\t\t\t}\n\t\t\tthrow new SearchProviderError(\"kagi\", err.message, err.statusCode);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/** Search provider for Kagi web search. */\nexport class KagiProvider extends SearchProvider {\n\treadonly id = \"kagi\";\n\treadonly label = \"Kagi\";\n\n\tisAvailable(authStorage: AuthStorage): boolean {\n\t\treturn authStorage.hasAuth(\"kagi\");\n\t}\n\n\tsearch(params: SearchParamsWithFetch): Promise<SearchResponse> {\n\t\tconst fetchImpl = params.fetch;\n\n\t\treturn searchKagi({","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/web/search/providers/kagi.ts#L50-L86","documentation":"Re-thrown by searchKagi as a SearchProviderError wrapping a KagiApiError whose statusCode is not classified by classifyProviderHttpError (i.e. an unexceptional or unusual HTTP status). The original Kagi error message is preserved verbatim.","triggerScenarios":"Kagi API request fails with a KagiApiError carrying a numeric statusCode that classifyProviderHttpError does not map (e.g. 4xx/5xx statuses outside the classified set like 402, 418, 520), so the raw error is wrapped and thrown.","commonSituations":"Kagi billing/quota states (402 payment required), unusual upstream errors behind Kagi, novel status codes not yet in the classification table.","solutions":["Read err.status (the wrapped statusCode) and err.message for the actual Kagi failure reason","Check your Kagi API key balance/subscription if the message indicates payment or quota","Retry with backoff for 5xx-class statuses","Add the unclassified status to classifyProviderHttpError if it should map to a richer error"],"exampleFix":"// before\nconst results = await searchKagi({ query, authStorage });\n// after\ntry {\n  const results = await searchKagi({ query, authStorage });\n} catch (err) {\n  if (err instanceof SearchProviderError && err.status === 402) {\n    // Kagi subscription issue — check billing\n  }\n}","handlingStrategy":"try-catch","validationCode":"// preflight: verify Kagi key works with a cheap call\nconst ok = await fetch('https://kagi.com/api/v0/search?q=test', { headers: { Authorization: `Bot ${key}` } }).then(r => r.status !== 401);","typeGuard":"function isKagiApiError(e: unknown): e is { name: 'KagiApiError'; statusCode?: number; message: string } {\n  return e instanceof Error && e.name === 'KagiApiError';\n}","tryCatchPattern":"try {\n  const res = await searchKagi({ query, authStorage });\n} catch (err) {\n  if (err instanceof SearchProviderError && typeof err.status === 'number') {\n    if (err.status === 402) handleBilling();\n    else if (err.status >= 500) retryWithBackoff();\n    else throw err;\n  } else throw err;\n}","preventionTips":["Keep the Kagi subscription funded/active","Preflight the API key at startup","Retry only 5xx-class statuses with exponential backoff","Track unclassified status codes and extend classification as needed"],"tags":["api","http","kagi","error-wrapping"],"backgroundTag":"api-error-response-code","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}