{"record":{"id":"3ddc95a3897f5a42","repo":"decolua/9router","slug":"res-status-events-stream-failed","errorCode":null,"errorMessage":"[${res.status}] events stream failed","messagePattern":"\\[(.+?)\\] events stream failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/executors/trae.js","lineNumber":145,"sourceCode":"    const text = await res.text();\n    if (!res.ok) throw new Error(`[${res.status}] ${text}`);\n    const json = JSON.parse(text);\n    if (json?.code !== 0) throw new Error(`Trae create_session: ${JSON.stringify(json)}`);\n    return { sessionId: json.data.chat_session_id, messageId: json.data.message_id };\n  }\n\n  // GET /events SSE → invoke onEvent(eventType, dataObj) per frame.\n  // Resolves when `done`/`error` arrives, the stream ends, or timeout fires.\n  async streamEvents(headers, sessionId, replyTo, onEvent, signal) {\n    const url = `${this.base()}/chat_sessions/${sessionId}/events?reply_to_message_id=${encodeURIComponent(replyTo)}`;\n    const ctrl = new AbortController();\n    if (signal?.aborted) ctrl.abort();\n    const timer = setTimeout(() => ctrl.abort(new Error(\"trae stream timeout\")), STREAM_TIMEOUT_MS);\n    const onAbort = () => ctrl.abort();\n    if (signal) signal.addEventListener(\"abort\", onAbort, { once: true });\n    try {\n      const res = await proxyAwareFetch(url, { method: \"GET\", headers, signal: ctrl.signal }, null);\n      if (!res.ok || !res.body) throw new Error(`[${res.status}] events stream failed`);\n      const reader = res.body.getReader();\n      const decoder = new TextDecoder();\n      let buf = \"\";\n      let ev = null;\n      for (;;) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        buf += decoder.decode(value, { stream: true });\n        let nl;\n        while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n          const line = buf.slice(0, nl).replace(/\\r$/, \"\");\n          buf = buf.slice(nl + 1);\n          if (line.startsWith(\"event:\")) ev = line.slice(6).trim();\n          else if (line.startsWith(\"data:\")) {\n            const payload = line.slice(5).trim();\n            let data;\n            try { data = JSON.parse(payload); } catch { data = { _raw: payload }; }\n            if (onEvent(ev, data)) {","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/executors/trae.js#L127-L163","documentation":"After creating a session, Trae streams results over a GET /events SSE connection. streamEvents() aborts on timeout/signal and throws this error when the events response is not OK or has no body, embedding only the status code — without a readable event stream the executor cannot deliver any response.","triggerScenarios":"The GET /events request returns a non-2xx status (session already expired, auth rejected, 429, 5xx) or succeeds without a body (res.body null, e.g. some proxies stripping streaming responses). Raised from streamEvents, called by sse/execute.","commonSituations":"Session expired between create_session and the events call (slow client or long queue); Trae rejecting the events request due to auth/region; a proxy or CDN buffering/stripping streaming bodies; transient 5xx right after session creation.","solutions":["Retry the whole request — a fresh create_session + events cycle fixes expired-session and transient 5xx cases","Check the status code: 401/403 → refresh the Trae credential; 404 → session expired, recreate session; 429 → back off","Remove/bypass proxies that strip or buffer streaming response bodies (res.body empty)","Reduce delay between session creation and the events call so the session cannot expire","Update 9router in case Trae changed the events endpoint or auth requirements"],"exampleFix":"// before\nconst events = await executor.sse(...); // throws raw\n// after\nlet events;\ntry { events = await executor.sse(...); }\ncatch (e) {\n  if (/\\[404\\] events stream failed/.test(e.message)) {\n    events = await retryWithNewSession(...); // full retry recreates the session\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await executor.sse(...);\n} catch (e) {\n  const status = +String(e.message).match(/^\\[(\\d+)\\]/)?.[1] ?? 0;\n  if (status === 404 || status >= 500) {\n    // session likely expired or transient — full retry recreates the session\n    return retryWithNewSession(req);\n  }\n  if (status === 401 || status === 403) { await refreshTraeCredential(); return retry(); }\n  throw e;\n}","preventionTips":["Open the events stream immediately after session creation to avoid expiry","Avoid proxies that strip or buffer streaming bodies","Refresh credentials on 401/403 before retrying","Treat timeouts and 5xx as retryable with a fresh session"],"tags":["trae","sse","stream","http-status","session"],"backgroundTag":"sse-stream-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}