{"record":{"id":"101c1844cf2c6f6e","repo":"Hmbown/CodeWhale","slug":"runtime-thread-response-is-not-an-event-stream","errorCode":null,"errorMessage":"Runtime thread response is not an event stream","messagePattern":"Runtime thread response is not an event stream","errorType":"http","errorClass":"RuntimeApiError","httpStatus":null,"severity":"error","filePath":"npm/runtime-sdk/index.js","lineNumber":145,"sourceCode":"  /** Read the existing durable thread journal. This never starts a turn. */\n  async *threadEvents(threadId, options = {}) {\n    const query = new URLSearchParams();\n    for (const [key, value] of [[\"since_seq\", options.sinceSeq], [\"replay_limit\", options.replayLimit]]) {\n      if (value === undefined) continue;\n      if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${key} must be a nonnegative safe integer`);\n      query.set(key, String(value));\n    }\n    if (options.includeProgress !== undefined && typeof options.includeProgress !== \"boolean\")\n      throw new TypeError(\"includeProgress must be a boolean\");\n    if (options.includeProgress) query.set(\"progress\", \"true\");\n    const path = `/v1/threads/${segment(threadId)}/events?${query}`;\n    const response = await this.#rawRequest(path, {\n      method: \"GET\", capability: \"thread_event_stream\", accept: \"text/event-stream\",\n      signal: options.signal, redirect: \"error\",\n    });\n    if (!response.body || !/^text\\/event-stream(?:;|$)/i.test(response.headers.get(\"content-type\") ?? \"\")) {\n      await response.body?.cancel();\n      throw new RuntimeApiError(\"Runtime thread response is not an event stream\", { method: \"GET\", path });\n    }\n    if (options.includeProgress && response.headers.get(\"x-codewhale-event-progress\") !== \"1\") {\n      await response.body.cancel();\n      throw new RuntimeCapabilityError(\"thread_event_progress\", \"Runtime does not support thread replay progress\", { method: \"GET\", path, status: 501 });\n    }\n    yield* parseEventStream(response.body, { maxFrameChars: 2 * 1024 * 1024, requireBoundary: true });\n  }\n\n  async #jsonRequest(path, options = {}) {\n    const response = await this.#rawRequest(path, options);\n    if (response.status === 204) {\n      return null;\n    }\n    return response.json();\n  }\n\n  async #rawRequest(path, options = {}) {\n    const method = options.method ?? \"GET\";","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/npm/runtime-sdk/index.js#L127-L163","documentation":"After issuing the GET events request, threadEvents() verifies the response is actually a text/event-stream with a readable body. If the content-type is not SSE (or the body is missing), it cancels the body and throws RuntimeApiError. This typically means an intermediary or the server responded with a regular HTTP response (HTML error page, JSON error) instead of the event stream the SDK requires.","triggerScenarios":"Calling threadEvents() against a runtime that does not implement the thread_event_stream capability and returns a 404/JSON body; a proxy or auth gateway intercepting the request and returning an HTML error page; misconfigured base URL pointing at a non-runtime endpoint.","commonSituations":"Reverse proxy returning an error/login page; older runtime version without SSE support on /v1/threads/:id/events; wrong port or path prefix in baseUrl.","solutions":["Verify the base URL points at the Codewhale runtime API and the runtime supports thread_event_stream.","Check the runtime version; upgrade to one that implements the thread event SSE endpoint.","Inspect response handling in your proxy/auth layer so SSE responses pass through unmodified.","Catch RuntimeApiError and log body/status from the error options to see what was actually returned."],"exampleFix":"// before\nconst events = client.threadEvents(id);\n// after\ntry {\n  for await (const ev of client.threadEvents(id)) handle(ev);\n} catch (err) {\n  if (err instanceof RuntimeApiError) console.error(err.message, err.status, err.body);\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { for await (const ev of client.threadEvents(id)) handle(ev); } catch (err) { if (err instanceof RuntimeApiError && err.message.includes(\"not an event stream\")) { log.error(\"non-SSE response from runtime\", err.status, err.body); } throw err; }","preventionTips":["Smoke-test the runtime's /v1/threads/:id/events endpoint returns content-type text/event-stream before wiring consumers.","Configure proxies to pass SSE responses through without buffering or HTML error interception.","Pin SDK and runtime versions together and verify capability support on deploy."],"tags":["sse","content-type","network","runtime-api"],"backgroundTag":"unexpected-response-shape","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}