datawhalechina/hello-agents · error · ApiError
请求失败(${res.status})
Error message
请求失败(${res.status}) What it means
Fallback branch of a typed ApiClient: when the response is not OK and the parsed payload has neither a string detail nor a string message field, it throws new ApiError('请求失败(status)', status). The client also wraps calls in a timeout (AbortController + timer cleared in finally), so this is the terminal branch of a three-way error taxonomy: API error / timeout / network error.
Source
Thrown at Co-creation-projects/aatanxiao12-beep-YingQian/frontend/src/api/client.ts:55
} catch {
payload = { message: text }
}
}
if (!res.ok) {
const detail =
payload &&
typeof payload === 'object' &&
'detail' in payload &&
typeof (payload as { detail: unknown }).detail === 'string'
? (payload as { detail: string }).detail
: payload &&
typeof payload === 'object' &&
'message' in payload &&
typeof (payload as { message: unknown }).message === 'string'
? (payload as { message: string }).message
: `请求失败(${res.status})`
throw new ApiError(detail, res.status)
}
return payload as T
} catch (err) {
if (err instanceof ApiError) throw err
if (err instanceof DOMException && err.name === 'AbortError') {
throw new ApiError('请求超时,请稍后重试或检查后端服务', 408)
}
throw new ApiError(err instanceof Error ? err.message : '网络异常', 0)
} finally {
window.clearTimeout(timer)
}
}
View on GitHub (pinned to 606a07d341)
Solutions
- curl -i the failing endpoint and inspect the error body shape; extend the detail extraction to handle array/object detail
- If detail is FastAPI's 422 array, map it to a readable string before throwing
- Align the frontend error envelope expectation with the backend contract (document detail/message in one place)
- Keep the status on ApiError (it already is) and branch UI messaging on it
Example fix
// before
const detail = ... typeof (payload as ...).detail === 'string' ? ... : `请求失败(${res.status})`;
// after
let detail: string;
if (typeof payload?.detail === 'string') detail = payload.detail;
else if (Array.isArray(payload?.detail)) detail = payload.detail.map((d: any) => `${(d.loc || []).join('.')}: ${d.msg}`).join('; ');
else if (typeof payload?.message === 'string') detail = payload.message;
else detail = `请求失败(${res.status})`; Defensive patterns
Strategy: type-guard
Type guard
type ApiErrBody = { detail?: string | Array<{ loc?: string[]; msg?: string }>; message?: string }; function extractDetail(b: ApiErrBody | null, status: number): string { if (typeof b?.detail === 'string') return b.detail; if (Array.isArray(b?.detail)) return b.detail.map(d => `${(d.loc||[]).join('.')}: ${d.msg}`).join('; '); if (typeof b?.message === 'string') return b.message; return `请求失败(${status})`; } Try / catch
try { await client.get<T>('/x'); } catch (e) { if (e instanceof ApiError && e.status === 401) redirectLogin(); else toast(e.message); } Prevention
- Define the error envelope once and reuse the extractor
- Keep ApiError.status on every throw for branching
- Contract-test error responses, not just success responses
When it happens
Trigger: Backend error responses whose body is not the expected envelope: empty bodies, JSON with numeric or object detail, plain-text errors from gateways, HTML error pages that somehow parsed. E.g. FastAPI 422 where detail is an array (not string), so the typeof check fails and this generic message is used.
Common situations: FastAPI validation errors (detail is an array of objects), upstream gateway text errors, auth middleware returning {error: '...'} instead of detail/message, backend version returning a different error envelope.
Related errors
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/e968bcee45703830.
Report an issue: GitHub.