moeru-ai/airi · error · Error
HTTP ${res.status}: ${await readErrorDetail(res)}
Error message
HTTP ${res.status}: ${await readErrorDetail(res)} What it means
Thrown by readJsonOrThrow() in cloud-mapper.ts when a /api/v1/chats request (GET list, POST create) returns non-2xx. It reads the error body as JSON and prefers body.message then body.error then statusText, so the server's structured error is surfaced. Used by listChats() and the createChat() success path; the 409 idempotent path is handled separately before this can fire for create.
Source
Thrown at packages/stage-ui/src/libs/chat-sync/cloud-mapper.ts:94
interface ApiErrorBody {
error?: string
message?: string
}
async function readErrorDetail(res: Response): Promise<string> {
try {
const body = await res.json() as ApiErrorBody
return body.message ?? body.error ?? res.statusText
}
catch {
// Non-JSON body — keep statusText.
return res.statusText
}
}
async function readJsonOrThrow<T>(res: Response, schema: v.BaseSchema<unknown, T, v.BaseIssue<unknown>>): Promise<T> {
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await readErrorDetail(res)}`)
}
// External boundary: validate the success shape too. A server schema drift
// would otherwise feed a structurally broken object into `reconcileLocalAndRemote`.
const raw: unknown = await res.json()
return v.parse(schema, raw)
}
async function throwOnError(res: Response): Promise<void> {
if (res.ok)
return
throw new Error(`HTTP ${res.status}: ${await readErrorDetail(res)}`)
}
/**
* Build a thin REST client over `/api/v1/chats` for cloud reconcile use cases.
*
* Use when:
* - The session store needs to mirror local sessions to the server `chats`View on GitHub (pinned to 27111382b4)
Solutions
- Check the HTTP status in the message: 401 → re-authenticate; 5xx → server issue; timeout → raise requestTimeoutMs or reduce payload.
- Confirm options.serverUrl is correct and reachable from the client.
- Ensure authedFetch is wired so Authorization is attached and 401 triggers a refresh before this throw.
- Inspect the server logs for the matching request to see the structured error detail.
Defensive patterns
Strategy: try-catch
Validate before calling
// ensure auth is wired and serverUrl is reachable before reconcile
if (!options.serverUrl) throw new Error('createCloudChatMapper: serverUrl required')
// authedFetch should attach Authorization and refresh on 401 before this throw fires Type guard
null
Try / catch
try {
const chats = await mapper.listChats()
}
catch (err) {
if (err instanceof Error && err.message.startsWith('HTTP 401')) {
// re-authenticate; the refresh cycle could not renew the token
}
else if (err instanceof Error && err.message.startsWith('HTTP ')) {
// log status/body; retry transient 5xx with backoff
}
else throw err
} Prevention
- Wire authedFetch so Authorization is attached and 401 triggers a refresh.
- Set a sensible requestTimeoutMs (default 10s) for your network.
- Validate the server response with the provided valibot schema so drift is caught at the boundary.
When it happens
Trigger: createCloudChatMapper's listChats() or createChat() receives a non-ok Response. Typical statuses: 401 (auth token expired and authedFetch refresh also failed), 403 (forbidden), 5xx (server error), network timeout via AbortSignal.timeout, or a non-JSON error body (falls back to statusText).
Common situations: Auth token expired and the refresh cycle could not renew it. Server /api/v1/chats route threw. Wrong serverUrl in mapper options. Request exceeded requestTimeoutMs (default 10s). Schema drift causing a 422 validation error upstream.
Related errors
- listAccounts failed
- unlinkAccount failed
- Failed to create token: ${JSON.stringify(response) || 'Unkno
- Gemini TTS request failed: ${response.status} ${await respon
- Failed to fetch voices: ${response.statusText}
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/867dab1a523199a9.
Report an issue: GitHub.