{"record":{"id":"e968bcee45703830","repo":"datawhalechina/hello-agents","slug":"res-status","errorCode":null,"errorMessage":"请求失败（${res.status}）","messagePattern":"请求失败（(.+?)）","errorType":"exception","errorClass":"ApiError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/aatanxiao12-beep-YingQian/frontend/src/api/client.ts","lineNumber":55,"sourceCode":"      } catch {\n        payload = { message: text }\n      }\n    }\n\n    if (!res.ok) {\n      const detail =\n        payload &&\n        typeof payload === 'object' &&\n        'detail' in payload &&\n        typeof (payload as { detail: unknown }).detail === 'string'\n          ? (payload as { detail: string }).detail\n          : payload &&\n              typeof payload === 'object' &&\n              'message' in payload &&\n              typeof (payload as { message: unknown }).message === 'string'\n            ? (payload as { message: string }).message\n            : `请求失败（${res.status}）`\n      throw new ApiError(detail, res.status)\n    }\n\n    return payload as T\n  } catch (err) {\n    if (err instanceof ApiError) throw err\n    if (err instanceof DOMException && err.name === 'AbortError') {\n      throw new ApiError('请求超时，请稍后重试或检查后端服务', 408)\n    }\n    throw new ApiError(err instanceof Error ? err.message : '网络异常', 0)\n  } finally {\n    window.clearTimeout(timer)\n  }\n}\n","sourceCodeStart":37,"sourceCodeEnd":69,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/aatanxiao12-beep-YingQian/frontend/src/api/client.ts#L37-L69","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst detail = ... typeof (payload as ...).detail === 'string' ? ... : `请求失败（${res.status}）`;\n\n// after\nlet detail: string;\nif (typeof payload?.detail === 'string') detail = payload.detail;\nelse if (Array.isArray(payload?.detail)) detail = payload.detail.map((d: any) => `${(d.loc || []).join('.')}: ${d.msg}`).join('; ');\nelse if (typeof payload?.message === 'string') detail = payload.message;\nelse detail = `请求失败（${res.status}）`;","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"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}）`; }","tryCatchPattern":"try { await client.get<T>('/x'); } catch (e) { if (e instanceof ApiError && e.status === 401) redirectLogin(); else toast(e.message); }","preventionTips":["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"],"tags":["typescript","fetch","http","error-envelope"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}