star7th/showdoc · error · Error
HTTP ${res.status}: ${res.statusText}
Error message
HTTP ${res.status}: ${res.statusText} What it means
Thrown by the frontend SSE client in sendAgentMessage() after POSTing JSON to {serverHost}/api/agent/agent: the fetch resolved but res.ok was false, i.e. the server answered with a 4xx/5xx status, so the code discards the response and throws `HTTP <status>: <statusText>`. Note that over HTTP/2 statusText is always empty, so the real message often looks like `HTTP 401: ` or `HTTP 500: `. The SSE stream is never opened; params.onError receives this Error.
Source
Thrown at web_src/src/api/aiAgent.ts:271
const url = serverHost + '/api/agent/agent'
// 异步执行,不 await
;(async () => {
let reader: ReadableStreamDefaultReader<any> | null = null
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'text/event-stream',
'Cache-Control': 'no-cache',
},
body: JSON.stringify(body),
signal: controller.signal,
})
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
}
if (!res.body) {
throw new Error('Response body is empty, server did not return a stream. Please try again later.')
}
reader = res.body.getReader()
const decoder = new TextDecoder('utf-8')
let buffer = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
// SSE 按双换行分割
const parts = buffer.split('\n\n')View on GitHub (pinned to 6a3fa91eee)
Solutions
- Log/inspect res.status and the response body (await res.text() before throwing) to identify the exact status; 401/403 means token, 404 means wrong server host/route, 500 means server-side logs (check PHP error log / bootstrap failure), 502/504 means proxy timeout.
- For 401/403: refresh user_token (re-login) or ensure getGuestToken() returns a valid guest token before calling sendAgentMessage.
- For 404: verify getServerHost() resolves to the server that actually exposes /api/agent/agent and that the server build is current.
- For 500: fix the server-side cause (see server logs; commonly the bootstrap DB failure in server/app/Common/bootstrap.php).
- For 502/504 on long turns: raise proxy_read_timeout and disable buffering for the /api/agent/ location (proxy_buffering off; X-Accel-Buffering: no already sent by the app).
- Include the status text and a short body excerpt in the thrown Error so users and logs see the real cause.
Example fix
// before
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`)
}
// after
if (!res.ok) {
const detail = await res.text().catch(() => '')
const err = new Error(`HTTP ${res.status}: ${res.statusText || ''} ${detail.slice(0, 200)}`.trim())
;(err as any).status = res.status
throw err
} Defensive patterns
Strategy: try-catch
Validate before calling
const userToken = getUserToken()
if (!userToken && !getGuestToken()) {
params.onError(new Error('Not authenticated: no user_token or guest_token available'))
return { abort: () => {} }
} Type guard
function isHttpStatusError(e: unknown): e is Error & { status: number } {
return e instanceof Error && /^HTTP \d{3}/.test(e.message) && typeof (e as any).status === 'number'
} Try / catch
// inside the existing async IIFE in sendAgentMessage, keep ONE catch that classifies:
try {
// ... fetch + SSE loop ...
} catch (e: any) {
if (e.name === 'AbortError') return // user-initiated abort, not an error
if (typeof e.status === 'number') {
if (e.status === 401 || e.status === 403) params.onError(new Error('Login expired, please re-authenticate'))
else if (e.status >= 500) params.onError(new Error('Server error, please retry later'))
else params.onError(e)
} else {
params.onError(e instanceof Error ? e : new Error(String(e)))
}
} Prevention
- Attach the numeric status to the thrown Error so callers can branch without string-matching the message.
- Read res.text() on non-OK responses before throwing; the server's error body often names the real cause.
- Validate token presence before opening the stream; refresh expired tokens proactively.
- For deployments behind nginx, configure proxy_buffering off and a proxy_read_timeout larger than the longest agent turn to avoid 502/504.
When it happens
Trigger: POST /api/agent/agent with an expired/invalid user_token or missing guest_token (server rejects auth -> 401/403); getServerHost() pointing at the wrong origin or an old deployment so the route 404s; a PHP fatal on the server (including the RuntimeException from server/app/Common/bootstrap.php when the SQLite/MySQL connection fails) surfacing as 500; a nginx/apache proxy in front of the PHP server timing out a long agent turn -> 502/504; request body rejected (413) when editor_content is very large.
Common situations: User left the tab open past token expiry and sends a new agent message; frontend deployed against a server that was rolled back or missing the agent routes; SQLite file permissions broke after a migration, making every server route return 500; reverse proxy (nginx) buffering/timeout defaults killing long-lived SSE POSTs; HTTP/2 deployments where statusText is empty making the error look malformed.
Related errors
AI-assisted analysis of star7th/showdoc@6a3fa91eee (2026-08-21).
Data as JSON: /api/errors/c2c9c52b53cd6a1a.
Report an issue: GitHub.