{"record":{"id":"a15c2c3042ba2185","repo":"moeru-ai/airi","slug":"failed-to-fetch-providers","errorCode":null,"errorMessage":"Failed to fetch providers","messagePattern":"Failed to fetch providers","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/services/inference-service-providers.ts","lineNumber":138,"sourceCode":"    if (item.validated)\n      status = 'configured'\n    else if (item.validationBypassed)\n      status = 'bypassed'\n\n    return {\n      id: item.id,\n      definitionId: item.definitionId,\n      config: item.config,\n      status,\n      configuredBy: getDefinedProvider(item.definitionId)?.configuredBy ?? 'user',\n    }\n  }\n\n  async function fetchRemote(client: InferenceServiceProvidersRemoteClient, options?: InferenceServiceProviderServiceOptions): Promise<InferenceServiceProviders> {\n    options?.abortSignal?.throwIfAborted()\n    const res = await client.api.v1.providers.$get(undefined, requestOptions(options))\n    if (!res.ok)\n      throw new Error('Failed to fetch providers')\n\n    const data = await res.json() as unknown[]\n    options?.abortSignal?.throwIfAborted()\n\n    const providers: InferenceServiceProviders = {}\n    for (const item of data) {\n      const provider = normalize(item)\n      providers[provider.id] = provider\n    }\n    return providers\n  }\n\n  async function createRemote(client: InferenceServiceProvidersRemoteClient, provider: InferenceServiceProvider, options?: InferenceServiceProviderServiceOptions): Promise<InferenceServiceProvider> {\n    options?.abortSignal?.throwIfAborted()\n    const res = await client.api.v1.providers.$post({\n      json: {\n        id: provider.id,\n        definitionId: provider.definitionId,","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/moeru-ai/airi/blob/0616eabd5ba89c7bcef0eb1da84694b9c285c573/packages/stage-ui/src/services/inference-service-providers.ts#L120-L156","documentation":"Thrown by fetchRemote in the inference-service-providers service when GET /api/v1/providers (typed RPC client.api.v1.providers.$get) returns a non-2xx status. The service discards the status code, so auth failures, missing backend, and server errors all surface as this single message. It is the entry point for hydrating the remote provider list, so failures here usually blank the whole providers UI.","triggerScenarios":"Fetching providers when the hosted API is down or unreachable (5xx/network proxy errors); auth session expired (401) so the route rejects; backend deployed without the providers route or with a schema mismatch after a partial upgrade (404/500).","commonSituations":"App configured for the remote/server mode while docker-compose stack is stopped; token expired after long idle; server version older than the client expecting newer /api/v1/providers contract.","solutions":["Check the actual HTTP status via the network tab or by including res.status in the thrown error.","Start/verify the hosted backend stack and edge routing.","Re-authenticate if the status is 401/403.","Match client and server versions so the /api/v1/providers route exists and its response parses.","Fall back to local provider definitions while remote is unavailable, surfacing a retry action in the UI."],"exampleFix":"// before\nconst res = await client.api.v1.providers.$get(undefined, requestOptions(options))\nif (!res.ok)\n  throw new Error('Failed to fetch providers')\n\n// after\nconst res = await client.api.v1.providers.$get(undefined, requestOptions(options))\nif (!res.ok)\n  throw new Error(`Failed to fetch providers (HTTP ${res.status})`)","handlingStrategy":"retry","validationCode":"// Simple reachability check before hydrating providers\nawait fetch(apiBaseUrl, { method: 'HEAD', signal: abortSignal })\nconst providers = await providersService.fetchRemote(client, { abortSignal })","typeGuard":null,"tryCatchPattern":"let providers: InferenceServiceProviders | undefined\nfor (let attempt = 0; attempt < 3 && !providers; attempt++) {\n  try {\n    providers = await providersService.fetchRemote(client, { abortSignal })\n  }\n  catch (error) {\n    if (abortSignal?.aborted || attempt === 2)\n      throw error\n    await delay(500 * 2 ** attempt) // backoff before retry\n  }\n}","preventionTips":["Show remote provider state as 'unavailable' rather than empty when this fails.","Retry with backoff for transient 5xx/network failures; do not retry 401 — re-auth instead.","Keep client and server versions aligned so route contracts match.","Cache the last successful provider list to keep the UI usable during outages."],"tags":["http","api-client","providers","remote","hono-rpc"],"backgroundTag":"http-error-status","analyzedSha":"0616eabd5ba89c7bcef0eb1da84694b9c285c573","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}