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

  1. curl -i the failing endpoint and inspect the error body shape; extend the detail extraction to handle array/object detail
  2. If detail is FastAPI's 422 array, map it to a readable string before throwing
  3. Align the frontend error envelope expectation with the backend contract (document detail/message in one place)
  4. 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

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.