Skip to content

fix(realtime): preserve base URL queries in WebSocket upgrades - #3971

Merged
markstuart-oai merged 1 commit into
mainfrom
codex/sdk-1006-realtime-wire-contract
Sep 27, 2026
Merged

markstuart-oai merged 1 commit into
mainfrom
codex/sdk-1006-realtime-wire-contract

Conversation

@markstuart-oai

Copy link
Copy Markdown
Contributor

A custom base URL such as https://example.test/v1/customer?tenant=sample failed to open Realtime WebSockets. Both the base URL normalization and sync/async Realtime path preparation appended to HTTPX's raw_path, which includes the query. As a result, the server received /v1/customer instead of /v1/customer/realtime.

Append the slash and endpoint before the existing query, preserving the original query and connection options. Four real WebSocket tests verify the path, authentication and header overrides, configured compression, outbound messages, same-connection recovery after an API error, unknown fields and clean close.

Validation:

  • Before: both query-bearing tests failed, both plain-URL cases passed.
  • After: 149 affected Realtime/WebSocket/HTTPX/redirect/region tests and 12 client base URL tests passed under each of Pydantic 1 and 2. All four final tests passed on both.
  • Full Ruff, mypy and Pyright passed.

No new API or schema changes. Shared base URL normalization also applies to HTTP clients; broader HTTP relative-query behavior is outside this change.

@markstuart-oai
markstuart-oai requested a review from a team as a code owner September 27, 2026 18:14
@openai-sdks

openai-sdks Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

OkTest Summary

✅ 236/236 SDK tests passed in 11.716s for Python SDK PR #3971.

Test results — 42 files
Test Result Time
tests/chat-completions-complex-body.test.ts ✅ Passed 248ms
tests/chat-completions-create.test.ts ✅ Passed 210ms
tests/chat-completions-stream.test.ts ✅ Passed 432ms
tests/files-content-binary.test.ts ✅ Passed 244ms
tests/files-create-multipart.test.ts ✅ Passed 296ms
tests/files-list-pagination.test.ts ✅ Passed 185ms
tests/initialize-config.test.ts ✅ Passed 180ms
tests/instance-isolation.test.ts ✅ Passed 191ms
tests/models-list.test.ts ✅ Passed 279ms
tests/responses-background-lifecycle.test.ts ✅ Passed 209ms
tests/responses-body-method-errors.test.ts ✅ Passed 378ms
tests/responses-cancel-timeout.test.ts ✅ Passed 205ms
tests/responses-cancel.test.ts ✅ Passed 166ms
tests/responses-compact-retries.test.ts ✅ Passed 271ms
tests/responses-compact.test.ts ✅ Passed 249ms
tests/responses-create-advanced-stream.test.ts ✅ Passed 164ms
tests/responses-create-advanced.test.ts ✅ Passed 203ms
tests/responses-create-disconnect.test.ts ✅ Passed 1.384s
tests/responses-create-errors.test.ts ✅ Passed 222ms
tests/responses-create-malformed-api-responses.test.ts ✅ Passed 491ms
tests/responses-create-retries.test.ts ✅ Passed 264ms
tests/responses-create-stream-failures.test.ts ✅ Passed 259ms
tests/responses-create-stream-timeout.test.ts ✅ Passed 273ms
tests/responses-create-stream-wire.test.ts ✅ Passed 3.503s
tests/responses-create-stream.test.ts ✅ Passed 393ms
tests/responses-create-terminal-states.test.ts ✅ Passed 204ms
tests/responses-create-timeout.test.ts ✅ Passed 197ms
tests/responses-create.test.ts ✅ Passed 335ms
tests/responses-delete.test.ts ✅ Passed 233ms
tests/responses-input-items-errors.test.ts ✅ Passed 799ms
tests/responses-input-items-list.test.ts ✅ Passed 452ms
tests/responses-input-items-options.test.ts ✅ Passed 362ms
tests/responses-input-tokens-count-timeout.test.ts ✅ Passed 248ms
tests/responses-input-tokens-count.test.ts ✅ Passed 160ms
tests/responses-malformed-inputs.test.ts ✅ Passed 2.508s
tests/responses-not-found-errors.test.ts ✅ Passed 279ms
tests/responses-parse.test.ts ✅ Passed 209ms
tests/responses-retrieve-retries.test.ts ✅ Passed 197ms
tests/responses-retrieve.test.ts ✅ Passed 170ms
tests/responses-stored-method-errors.test.ts ✅ Passed 558ms
tests/retry-behavior.test.ts ✅ Passed 3.315s
tests/sdk-error-shape.test.ts ✅ Passed 295ms

View OkTest run #36339892924

SDK merge (dba1c2976759) · head (a5a6f80b56b8) · base (43443d14c5ab) · OkTest (f9111d4e2fcd)

@github-actions

Copy link
Copy Markdown
Contributor

Castiron custom code

✅ No new custom-code files detected.

47 mixed files remain; 1 existing customization changed.

Compared 43443d14c5ab → a5a6f80b56b8. Generated baselines verified.

File Result Current custom patch
src/openai/resources/realtime/realtime.py Existing customization changed +123 / −47
46 existing customizations unchanged
  • api.md
  • scripts/castiron/README.md
  • scripts/castiron/custom_code_report.py
  • scripts/castiron/test_custom_code_report.py
  • src/openai/init.py
  • src/openai/_client.py
  • src/openai/resources/audio/transcriptions.py
  • src/openai/resources/audio/translations.py
  • src/openai/resources/beta/agents/sessions/sessions.py
  • src/openai/resources/beta/beta.py
  • src/openai/resources/beta/responses/responses.py
  • src/openai/resources/beta/threads/runs/runs.py
  • src/openai/resources/beta/threads/threads.py
  • src/openai/resources/chat/completions/completions.py
  • src/openai/resources/embeddings.py
  • src/openai/resources/files.py
  • src/openai/resources/live/forks.py
  • src/openai/resources/live/live.py
  • src/openai/resources/live/sideband.py
  • src/openai/resources/realtime/api.md
  • src/openai/resources/responses/responses.py
  • src/openai/resources/uploads/uploads.py
  • src/openai/resources/vector_stores/file_batches.py
  • src/openai/resources/vector_stores/files.py
  • src/openai/resources/videos.py
  • src/openai/resources/webhooks/init.py
  • src/openai/resources/webhooks/webhooks.py
  • src/openai/types/beta/agent_session_message.py
  • src/openai/types/chat/init.py
  • src/openai/types/chat/chat_completion_message_tool_call.py
  • src/openai/types/fine_tuning/fine_tuning_job_integration.py
  • src/openai/types/realtime/conversation_item_input_audio_transcription_delta_event.py
  • src/openai/types/realtime/realtime_error_event.py
  • src/openai/types/responses/init.py
  • src/openai/types/responses/response.py
  • src/openai/types/responses/response_function_web_search.py
  • src/openai/types/responses/response_function_web_search_param.py
  • src/openai/types/responses/responses_client_event.py
  • src/openai/types/responses/responses_client_event_param.py
  • src/openai/types/responses/tool.py

6 more in the full report.

A changed generated baseline means this report cannot reliably identify which handwritten lines changed.

Inspect the custom-code diff

Download the exact patch produced by this run (requires repository access):

gh run download 36339913572 --repo openai/openai-python \
  --name castiron-custom-code-36339913572-1 --dir /tmp/castiron-custom-code-36339913572-1
git apply --stat /tmp/castiron-custom-code-36339913572-1/custom-code.patch
cat /tmp/castiron-custom-code-36339913572-1/custom-code.patch

Or reproduce it from an SDK checkout containing the vendored reporter:

git fetch --no-tags origin 43443d14c5ab8b9bc9d7aaf31263351f071afca2 a5a6f80b56b834bf524acfda149bdfd95c68fe53
python3 scripts/castiron/custom_code_report.py report \
  --base 43443d14c5ab8b9bc9d7aaf31263351f071afca2 \
  --head a5a6f80b56b834bf524acfda149bdfd95c68fe53 --fetch --require-head-hash --public \
  --out /tmp/castiron-custom-code-a5a6f80b56b8
cat /tmp/castiron-custom-code-a5a6f80b56b8/custom-code.patch

This is the current full custom patch for mixed files, not an attribution of only the handwritten lines changed by this PR.

Full report and patch

@jbeckwith-oai jbeckwith-oai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed a5a6f80. No blocking findings.

Splitting the raw path from its query before adding the trailing slash and Realtime endpoint fixes the malformed upgrade URL without changing header precedence or recoverable-error behavior. The sync/async loopback tests cover a custom path with and without base query parameters, one Authorization header, option/header preservation, typed error recovery, unknown fields, and same-socket reuse. I also checked the shared normalization callers and server failure/cleanup handling.

Validation: 64 controlled cases using the exact extracted Realtime URL methods and 16 shared-normalization cases passed with installed HTTPX 0.28.1, including escaped path characters, query values ending in '/', empty values, fragments, HTTP/HTTPS, and explicit WebSocket bases. All three changed Python files compile in memory. These are isolated URL-method probes, not a local full-SDK run; exact-head hosted Python 3.10/3.14, HTTPX2, lint/build and Castiron checks passed. No live API calls.

@markstuart-oai
markstuart-oai added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit f7bd4a7 Sep 27, 2026
26 checks passed
@markstuart-oai
markstuart-oai deleted the codex/sdk-1006-realtime-wire-contract branch September 27, 2026 18:59
@openai-sdks openai-sdks Bot mentioned this pull request Sep 26, 2026
pull Bot pushed a commit to Mattlk13/openai-python that referenced this pull request Sep 27, 2026
…nai#3972)

Stacked on openai#3971, which fixes base URL normalization. Once that is
merged, this PR can be retargeted to main.

Live WebSocket primary, fork and sideband managers appended their
endpoint after the base URL query. For example,
/v1/customer?tenant=sample dialed /v1/customer instead of the Live
endpoint. Append the path before the query in all six sync/async
managers.

Tests cover the actual WebSocket upgrade, preserved query parameters and
escaped session IDs, caller-initiated primary and fork starts, immediate
sideband events without waiting for session.started, follow-up updates
and clean close.

Validation: all six query cases failed and six plain cases passed before
the fix. After it, all 363 affected tests and 12 base URL tests passed
on both Pydantic v1 and v2. Full Ruff, mypy and Pyright passed. No
hosted-service or stored-fork-eligibility claims.
pull Bot pushed a commit to pdibenedetto/openai-python that referenced this pull request Sep 28, 2026
Automated Release PR
---


##
[3.20.0](openai/openai-python@v3.19.2...v3.20.0)
(2026-09-28)


### Features

* **api:** add Agents credential and session options
([openai#3967](openai#3967))
([bb68198](openai@bb68198))
* **api:** add Cyber access programs to Responses
([openai#3956](openai#3956))
([09c5b6f](openai@09c5b6f))
* **responses:** opt in to incremental WebSocket text and tool snapshots
([openai#3973](openai#3973))
([d0207b4](openai@d0207b4))
* **responses:** preserve detailed WebSocket accumulator snapshots
([openai#3981](openai#3981))
([a380cf2](openai@a380cf2))


### Bug Fixes

* **client:** retry unmapped TLS transport errors
([openai#3982](openai#3982))
([0d35a26](openai@0d35a26))
* **live:** avoid hangs at fractional transcript grouping deadlines
([openai#3970](openai#3970))
([4ef4129](openai@4ef4129))
* **live:** keep query parameters out of WebSocket endpoint paths
([openai#3972](openai#3972))
([f9c458b](openai@f9c458b))
* **live:** preserve caller queues and prevent uncertain WebSocket
replay ([openai#3980](openai#3980))
([80e9686](openai@80e9686))
* **realtime:** preserve base URL queries in WebSocket upgrades
([openai#3971](openai#3971))
([f7bd4a7](openai@f7bd4a7))
* **realtime:** retain configured queues without replaying attempted
sends ([openai#3978](openai#3978))
([a52805c](openai@a52805c))


### Chores

* **api:** clarify documented API error responses
([openai#3965](openai#3965))
([384fee3](openai@384fee3))
* **api:** document batch error responses
([openai#3961](openai#3961))
([6e4a79c](openai@6e4a79c))
* **api:** document files and uploads error responses
([openai#3960](openai#3960))
([a9d727f](openai@a9d727f))
* **api:** document fine-tuning and model errors
([openai#3964](openai#3964))
([5d4003c](openai@5d4003c))
* **api:** document Responses not-found errors
([openai#3959](openai#3959))
([63099e7](openai@63099e7))
* **api:** document stored chat completion errors
([openai#3963](openai#3963))
([a73fe0c](openai@a73fe0c))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: openai-sdks[bot] <284451331+openai-sdks[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants