moeru-ai/airi · error · Error
Failed to fetch providers
Error message
Failed to fetch providers
What it means
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.
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.
Example fix
// before
const res = await client.api.v1.providers.$get(undefined, requestOptions(options))
if (!res.ok)
throw new Error('Failed to fetch providers')
// after
const res = await client.api.v1.providers.$get(undefined, requestOptions(options))
if (!res.ok)
throw new Error(`Failed to fetch providers (HTTP ${res.status})`) Defensive patterns
Strategy: retry
Validate before calling
// Simple reachability check before hydrating providers
await fetch(apiBaseUrl, { method: 'HEAD', signal: abortSignal })
const providers = await providersService.fetchRemote(client, { abortSignal }) Try / catch
let providers: InferenceServiceProviders | undefined
for (let attempt = 0; attempt < 3 && !providers; attempt++) {
try {
providers = await providersService.fetchRemote(client, { abortSignal })
}
catch (error) {
if (abortSignal?.aborted || attempt === 2)
throw error
await delay(500 * 2 ** attempt) // backoff before retry
}
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Failed to add provider
- Failed to bookmark character
- Failed to like character
- Failed to remove provider
- Failed to update provider config
AI-assisted analysis of moeru-ai/airi@0616eabd5b (2026-08-18).
Data as JSON: /api/errors/a15c2c3042ba2185.
Report an issue: GitHub.
Appendix: source
Thrown at packages/stage-ui/src/services/inference-service-providers.ts:138
if (item.validated)
status = 'configured'
else if (item.validationBypassed)
status = 'bypassed'
return {
id: item.id,
definitionId: item.definitionId,
config: item.config,
status,
configuredBy: getDefinedProvider(item.definitionId)?.configuredBy ?? 'user',
}
}
async function fetchRemote(client: InferenceServiceProvidersRemoteClient, options?: InferenceServiceProviderServiceOptions): Promise<InferenceServiceProviders> {
options?.abortSignal?.throwIfAborted()
const res = await client.api.v1.providers.$get(undefined, requestOptions(options))
if (!res.ok)
throw new Error('Failed to fetch providers')
const data = await res.json() as unknown[]
options?.abortSignal?.throwIfAborted()
const providers: InferenceServiceProviders = {}
for (const item of data) {
const provider = normalize(item)
providers[provider.id] = provider
}
return providers
}
async function createRemote(client: InferenceServiceProvidersRemoteClient, provider: InferenceServiceProvider, options?: InferenceServiceProviderServiceOptions): Promise<InferenceServiceProvider> {
options?.abortSignal?.throwIfAborted()
const res = await client.api.v1.providers.$post({
json: {
id: provider.id,
definitionId: provider.definitionId,View on GitHub (pinned to 0616eabd5b)