yikart/AiToEarn · error · Error

HTTP ${response.status}: ${errorText}

Error message

HTTP ${response.status}: ${errorText}

What it means

thrown by the SSE onopen handler in ai.api.ts when the fetch-based EventSource receives an HTTP 4xx status (except 429, which is treated as retryable). It reads the response body as errorText and embeds it in the message so the server's error reason is preserved. Client errors are deliberately not retried.

Source

Thrown at project/aitoearn-web/src/api/ai/ai.api.ts:131

          'Authorization': `Bearer ${useUserStore.getState().token || ''}`,
          'Accept-Language': lng,
        },
        body: JSON.stringify(params),
        signal: abortController.signal,
        openWhenHidden: true,

        // 当连接打开时
        async onopen(response) {
          if (response.ok) {
            return // 一切正常,继续处理消息
          }

          // 处理错误响应
          if (response.status >= 400 && response.status < 500 && response.status !== 429) {
            // 客户端错误,不重试
            const errorText = await response.text()
            console.error('[SSE] Client error:', response.status, errorText)
            throw new Error(`HTTP ${response.status}: ${errorText}`)
          }
          else {
            // 服务器错误或其他问题,不自动重试,直接抛出错误
            console.error('[SSE] Server error:', response.status)
            throw new Error(`HTTP ${response.status}`)
          }
        },

        // 当收到消息时
        onmessage(event) {
          // 如果已完成,忽略后续消息
          if (isCompleted) {
            return
          }

          // 如果没有数据,跳过
          if (!event.data) {
            return

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Read the errorText after 'HTTP <status>: ' to see the server's actual rejection reason.
  2. Refresh the auth token / re-login if status is 401 or 403.
  3. Validate the request payload (model, conversationId, messages) against the current backend API.
  4. Check proxy/gateway routing config if status is 404 and other endpoints work.
  5. Add client-side handling to surface this message to the UI instead of an unhandled rejection.

Example fix

// before
onopen(response) { /* no auth pre-check */ }
// after
if (isTokenExpired()) await refreshToken()
await openSSEStream(...)
Defensive patterns

Strategy: try-catch

Validate before calling

// before opening the stream
if (!token) throw new Error('未登录,无法建立 AI 连接')
if (!navigator.onLine) throw new Error('网络不可用')

Type guard

function isClientHttpError(e: unknown): e is Error & { status: number } {
  const m = e instanceof Error && /^HTTP (4\d\d):/.exec(e.message)
  return !!m && e instanceof Error
}

Try / catch

try {
  await openAiSseStream(params)
}
catch (e) {
  const m = /^HTTP (\d{3}):/.exec(e instanceof Error ? e.message : '')
  const status = m ? Number(m[1]) : 0
  if (status === 401 || status === 403) await reloginAndRetry()
  else if (status === 0 || status >= 500) queueRetry()
  else showError(e)
}

Prevention

When it happens

Trigger: Opening an AI chat SSE stream when the server returns 400/401/403/404 etc., e.g. invalid API key, malformed request body, expired token, or nonexistent endpoint.

Common situations: Expired or missing auth token in the SSE request; backend rejecting the chat payload (bad model name); proxy/gateway (nginx) returning 404 for the stream path; API version mismatch after deployment.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/cc6bbf450e367304. Report an issue: GitHub.