openai-python 3.14.x and openai-node 7.16/7.17: Stream Error Normalization, WebSocket Backpressure, and SSE Fixes
Four OpenAI SDK releases in three days, one theme: reliability — Python normalizes stream errors into SDK exceptions, stringifies error codes, checks max_retries; Node caps WebSocket iterator backpressure and fixes lost terminal SSE events.
The official OpenAI SDKs shipped four releases in three days (2026-09-14 through 09-16), and unlike the recent wave of new API surfaces (prompt cache diagnostics, Live API, Agents API), this batch has a single theme: reliability. What exception does a mid-stream disconnect raise, what type is an error code, what happens when a WebSocket iterator falls behind, what if the server forgets to terminate the last SSE event — the exact problems production systems run into. This article traces every item to its commit, based on diffs and documentation extracted from the release tags on the day of writing.
1. Release timeline
| Package | Version | Released (UTC) | Theme |
|---|---|---|---|
| openai-python | v3.14.0 | 2026-09-14T23:28 | Stream error normalization (#3827) + string error codes |
| openai-python | v3.14.1 | 2026-09-15T23:12 | max_retries pre-validation (#3867) + parse commentary exception |
| openai-node | v7.16.0 | 2026-09-15T16:48 | WebSocket iterator backpressure (#2748) |
| openai-node | v7.17.0 | 2026-09-16T19:23 | Compaction progress event (#2749) + SSE fix and WS credential checks |
The two packages keep their non-matching version numbers, and one theme landing in different releases per package remains the norm; the rest of this article is grouped by package.
2. Python: stream consumption exceptions, normalized (#3827, v3.14.0)
This is the only feature-level Python change in the batch and the one with the widest production impact. Until now, transport-layer exceptions leaked out of Stream / AsyncStream consumption as raw httpx errors; from v3.14.0 they are normalized into SDK exceptions:
- Read timeouts raise
APITimeoutError; - Other httpx request failures raise
APIConnectionError; - The original exception is always available as
__cause__; - The README states it explicitly: stream consumption is not automatically retried — replaying a request could duplicate output already delivered to your application;
- Compatibility boundary: the Assistants event-handler helpers and raw
with_streaming_responseiterators keep their existing behavior (the former unwrap SDK exceptions back to the legacy transport exceptions).
A stream consumption pattern adapted from the README's error-handling section:
import openai
from openai import OpenAI
client = OpenAI()
try:
stream = client.responses.create(
model="gpt-5.6-terra",
input="Explain prompt caching to me",
stream=True,
)
for event in stream:
handle(event) # persist your position so you can resume after a disconnect
except openai.APITimeoutError as e:
print("read timeout, original exception:", e.__cause__)
except openai.APIConnectionError as e:
print("connection failed, original exception:", e.__cause__)
Note the README's wording — catch these SDK exceptions instead of raw HTTPX exceptions. If your except clauses still name httpx exception classes, they will simply stop matching after the upgrade.
3. Python: string error codes, retry validation, and the parse exception (v3.14.0 / v3.14.1)
Error codes normalized to strings (#3532, v3.14.0). The code property of body-bearing API error objects (APIStatusError and its subclasses) changes from pass-through to always-str: 404 becomes '404', 0 becomes '0', the empty string stays empty, and None stays None (each case covered by the official tests). This is the quiet kind of breaking change — code comparing code to an integer will not throw, it will just never match:
except openai.APIStatusError as e:
# from v3.14.0 on, e.code is always str or None
if e.code == "insufficient_quota": # string comparison
...
max_retries validated up front (#3867, v3.14.1). max_retries must be a non-negative integer: 0 disables retries; pass a large integer for a larger budget (the official test uses 10 to the power of 100); None raises TypeError (with a message suggesting 0 or a large integer) and negatives raise ValueError — all before the request is sent. The same commit tightens the retry loop itself: it now catches only transport request exceptions, so application exceptions raised by custom transports or hooks — including task-executor cancellation signals — propagate unchanged; the retry log line also gains a "retry i of N" progress indicator.
The parse commentary exception (#3861, v3.14.1). client.responses.parse(..., text_format=YourModel) used to attempt structured parsing on every text output item; now, among items carrying a phase marker, anything whose phase is not final_answer (commentary, for example) is left unparsed with parsed set to None — even when the text happens to match the schema — and a refusal does not cause commentary to be parsed as a fallback answer. output_text still concatenates all text including commentary, streamed text-done events follow the same rule, and outputs with a null phase keep the legacy behavior.
Other fixes in these releases (release-notes wording): bounded vector store file polling (#3401), normalized PathLike upload tuples (#3475), preserved response stream indexes after empty items (#3126), null-text handling in output_text (#3019), content filter errors now include the completion (#3094), and OPENAI_LOG gains warning / error / critical levels with invalid values ignored (#3734 — note it configures only the openai logger; configure HTTP transport loggers separately).
4. Node: maxBufferedEvents backpressure (#2748, v7.16.0)
Node-side WebSocket stream iterators previously buffered without bound — a slow consumer would eventually blow up memory or sink into backlog latency. v7.16.0 gives each iterator an independent backpressure cap, with the full semantics documented in docs/responses.md:
import OpenAI from 'openai';
import { ResponsesWS } from 'openai/resources/responses/ws';
const client = new OpenAI();
const socket = new ResponsesWS(client);
// Each iterator buffers independently; 256 is an example — size it to your processing capacity
for await (const event of socket.stream({ maxBufferedEvents: 256 })) {
handle(event);
}
Semantics, straight from the documentation:
- The count includes messages, raw data, errors, and lifecycle records (initial connection state, reconnecting, close);
- When the next record would exceed the limit, that iterator discards its backlog, removes its listeners, and rejects further
next()calls with aWebSocketError(message:WebSocket stream exceeded maxBufferedEvents (N)) — and a close record can overflow a full queue; - The shared socket and other iterators remain active; close the socket yourself when you no longer need it;
- The limit persists across reconnects and does not restart a failed iterator;
- It limits the event count, not payload bytes or total memory — one large message still counts as one record;
- Omitting the option (including iterating over the socket directly) leaves buffering unlimited;
- The option is also available on the beta Responses and Live WebSocket streams (the same commit touches the base classes of both);
- Validation happens before listeners are attached: anything that is not a positive safe integer throws
OpenAIError.
5. Node: SSE safety net and tighter WebSocket credentials (v7.17.0)
Terminal SSE events no longer dropped (#2726). SSE terminates an event with a blank line, but servers occasionally omit it after the final event — and the decoder used to swallow it, losing the terminating chunk that carries finish_reason. v7.17.0 adds flush() to the decoder: at end of stream, an in-progress event is emitted exactly once; records that already ended with a blank line are not delivered twice (flush returns null when nothing is in progress).
Function-backed API keys checked at WebSocket construction (#2586). For clients whose apiKey is a function (a key resolved per request), opening a WebSocket could previously fail after the connection was already established; now, if there is no resolved key (one resolved by a previous request and reusable) and no caller-supplied credential, construction throws before a socket is opened. The documented ways out: make a normal request first so the key resolves, pass a resolved Authorization header in the WebSocket options, use custom credential headers on compatible endpoints, or the Node ws transport's auth option.
Other items in this release (release-notes wording): chat runners preserve abort reasons (#2607), realtime preserves native WebSocket error causes (#2715), streaming runTools reject unfinished turns (#2716), zod strict schemas omit impossible optional branches (#2751), and the Responses API gains the compaction progress event (#2749 — covered in depth in its own article on this site). v7.16.0 also included: handling malformed WebSocket events and improved buffering (#2739), callback credentials kept local to each HTTP request (#2744), fallback abort subscriptions bounded with weak lifetimes (#2745), and linear-time trimming of Live transcript acknowledgment suffixes (#2740).
6. Upgrade notes: the behavior changes at a glance
| Change | Who hits it | What to do |
|---|---|---|
| Stream exceptions changed type (#3827) | streaming code whose except clauses name httpx exceptions | Catch APITimeoutError / APIConnectionError; build your own resume logic (the SDK will not retry a stream for you) |
error.code is a string now (#3532) | error handling comparing code to integers | Compare against strings; the None branch is unchanged |
max_retries validated earlier (#3867) | callers passing None, negatives, or floats; tests pinning the old None error message | Use 0 to disable retries; catch the validation error at construction / with_options time |
| WS credential check earlier (#2586) | function-backed apiKey clients opening WebSockets | Resolve the key first or pass credentials explicitly (header / ws auth) |
One sentence sums up the batch: move failures that used to explode mid-run to construction and request time, and turn leaked transport exceptions into programmable SDK exceptions — both steps toward production predictability.
7. Common pitfalls
- A mid-stream disconnect no longer enters your old except branch: the exception types were normalized; switch your capture list to the SDK exceptions and inspect
__cause__when debugging. - An error-code comparison that is always false:
codeis a string now; integer comparisons must become string comparisons. - Constructing a client raises on
max_retries: that is the new pre-request validation; check the argument type (a non-negative integer; use 0 to disable retries). - A
WebSocketErrorfrommaxBufferedEvents: the socket is not down — the shared connection is alive, but that iterator's backlog overflowed and was shed deliberately. Raise the cap or consume faster; other iterators and the socket are unaffected. - Opening a WebSocket throws with a function-backed key client: credential resolution moved to construction time; supply credentials explicitly via one of the four routes in section 5.
- No compaction progress event on the Python side: it has not landed as of v3.14.1 — see the cross-package section of our compaction article.
8. Next steps
- The Responses API Compaction Progress Event: response.compaction.compacting and compaction_trigger — a field-by-field read of node v7.17.0's headline feature.
- openai-python 3.9/3.10 and openai-node 7.11/7.12: Prompt Cache Diagnostics, API Key Expiry, GPT Image 2.5 — the previous installment (09-08 through 09-10) of this SDK series.
- OpenAI API Error Handling and Retry: 401/429/5xx Patterns — place this batch's exception normalization and retry validation into a complete error-handling system.
- Responses API advanced: structured outputs, streaming SSE, Batch API, prompt caching — the mechanics behind streaming and structured outputs (the parse behavior change is in section 3).
- The Live API lands in the OpenAI SDK: gpt-live-1, dual WebRTC/WebSocket channels, and SIP call control — maxBufferedEvents applies to Live WebSocket streams too.
- OpenAI Models Release Notes (2026, Living Document) — every release on one timeline.
Key points
- python v3.14.0 (#3827, the headline): consuming a Stream / AsyncStream raises APITimeoutError on read timeouts and APIConnectionError on other httpx request failures, with the original exception on __cause__; the README states stream consumption is not automatically retried — replaying could duplicate output already delivered
- python v3.14.0 (#3532): the code property of APIStatusError is normalized to strings — 404 becomes '404', 0 becomes '0', empty string and None stay as they are; code that compared code to integers must switch to string comparison
- python v3.14.1 (#3867): max_retries is validated before a request is sent — must be a non-negative integer, 0 disables retries, None raises TypeError and negatives raise ValueError; the retry loop now catches only transport request exceptions, so application exceptions from custom transports or hooks propagate unchanged
- python v3.14.1 (#3861): responses.parse leaves items whose phase is not final_answer (commentary) unparsed — parsed is always None even when the text happens to match the schema; output_text still concatenates all text
- node v7.16.0 (#2748): socket.stream({ maxBufferedEvents: 256 }) caps each iterator's backlog independently; the count includes messages, raw data, errors, and lifecycle records; on overflow the iterator discards its backlog and rejects next() with WebSocketError while the shared socket and other iterators stay active; the limit persists across reconnects and counts events, not bytes
- node v7.17.0 (#2726 / #2586): the SSE decoder flushes an in-progress event exactly once at EOF, so terminal events no longer vanish when the server omits the trailing blank line; function-backed API key clients without a resolved key or caller-supplied credential throw at WebSocket construction, before a socket is opened
Frequently asked questions
Official references
- Changelogopenai-python v3.14.0 Release Notes (GitHub)
- Changelogopenai-python v3.14.1 Release Notes (GitHub)
- Changelogopenai-node v7.16.0 Release Notes (GitHub)
- Changelogopenai-node v7.17.0 Release Notes (GitHub)
- Docsopenai-python commit d7c41ef: streaming — normalize errors raised while reading streams (#3827)
- Docsopenai-python commit f86c721: client — validate retry limits and preserve application errors (#3867)
- Docsopenai-node commit 76e4456: websocket — add per-iterator incoming event limits (#2748)
- Docsopenai-node commit 94d6418: streaming — emit terminal SSE events missing a trailing blank line (#2726)
- Docsopenai-node v7.17.0 docs/responses.md (official maxBufferedEvents section)
- Docsopenai-python v3.14.1 README.md (official error-handling section)
Related articles
OpenAI API 429 Rate Limit Errors: RateLimitError and SDK Retries Explained
A 429 is two problems in one status: throughput limits vs quota exhaustion. The Python SDK already retries twice and honors Retry-After — this guide explains the mechanics from source.
Read articleopenai-node v7.20.0 Explained: Environment-Variable Vault Credentials, External Storage, and Safety Cases
Six PRs in one openai-node release: environment_variable vault credentials, external storage management, safety case retrieval with two webhook events, a SIP media security field, and a legacy GET fix — each traced to PR and tag sources.
Read articleopenai-python 3.15/3.16 and openai-node 7.18/7.19: Cache Prewarming, Webhook Management, connector_id Deprecation
Six OpenAI SDK releases in one day: prewarm cache warming, client.webhooks endpoint management, connector_id deprecated for post-September-1 models, WebSocket sessions in both languages. Every item traced to its PR.
Read articleSubscribe to GPTMap Weekly
One email every Monday: curated OpenAI updates, deep dives, and best practices. No ads, unsubscribe anytime.
Submitting opens Buttondown in a new tab to confirm your subscription.