Skip to content

fix(azure): preserve deployment routing across copy/with_options - #3593

Merged
marcuswood-oai merged 3 commits into
openai:mainfrom
hsusul:fix/azure-copy-preserve-deployment
Sep 15, 2026
Merged

marcuswood-oai merged 3 commits into
openai:mainfrom
hsusul:fix/azure-copy-preserve-deployment

Conversation

@hsusul

@hsusul hsusul commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
  • I understand that this repository is auto-generated and my pull request may not be merged

Changes being requested

Fixes a routing regression in AzureOpenAI / AsyncAzureOpenAI after .copy() / .with_options().

This change is in hand-maintained code — src/openai/lib/azure.py has no File generated from our OpenAPI spec by Stainless header, and copy()/with_options() there is custom Azure logic, so it is not affected by codegen.

Problem

AzureOpenAI.copy() (aliased as with_options()) delegates to the base OpenAI.copy(), which reconstructs the client from base_url and does not pass azure_endpoint / azure_deployment back to AzureOpenAI.__init__. Because _azure_endpoint and _azure_deployment are only set from those constructor arguments, they get reset to None on the copied client.

_prepare_url() uses those attributes to bypass the deployment path for non-deployment endpoints:

if self._azure_deployment and self._azure_endpoint and url not in _deployments_endpoints:
    # -> {endpoint}/openai/{url}   (no /deployments/<name>/)

Once they are None, that bypass no longer runs, so a client configured with azure_deployment starts routing non-deployment endpoints (e.g. /models) under the deployment path, which 404s.

Reproduction (no network / key required)

from openai import AzureOpenAI

c = AzureOpenAI(
    azure_endpoint="https://example.openai.azure.com",
    azure_deployment="my-deploy",
    api_version="2024-06-01",
    azure_ad_token="fake-token",
)
print(c._prepare_url("/models"))
# https://example.openai.azure.com/openai/models        ✅

c2 = c.with_options(timeout=30)
print(c2._azure_endpoint, c2._azure_deployment)          # None None
print(c2._prepare_url("/models"))
# https://example.openai.azure.com/openai/deployments/my-deploy/models   ❌ (404)

Fix

Preserve _azure_endpoint / _azure_deployment on the copied client, unless the caller overrides base_url in the copy (in which case the old endpoint context is intentionally not carried over). Applied symmetrically to the sync and async clients.

Why minimal

  • Only copy() in each of the two Azure clients changes; two guarded lines each.
  • Public API, credential handling, and the mutually-exclusive base_url / azure_endpoint constructor contract are untouched (the fix deliberately avoids passing azure_endpoint alongside base_url).
  • Deployment endpoints (e.g. /chat/completions) are unaffected — base_url already encodes the deployment and _build_request continues to guard on "/deployments" in base_url.path.

Tests

Added test_copy_preserves_deployment_routing in tests/lib/test_azure.py (sync + async × copy / with_options), asserting that after a copy:

  • /models → {endpoint}/openai/models (not nested under /deployments/<name>/), and
  • /chat/completions still keeps the deployment path.

Fails on main (wrong /models URL), passes with this change.

Validation

  • rye run pytest tests/lib/test_azure.py → 63 passed
  • rye run pytest tests/lib/ → all pass except a pre-existing, unrelated failure (test_bedrock_auth_conformance.py::test_retry_signing_fixture, which fails identically on clean main in this environment)
  • ruff check / ruff format clean on both files
  • pyright and mypy clean on src/openai/lib/azure.py

Compatibility

No public API change; behavior only changes for the previously-broken post-copy case. Copies that override base_url keep their current behavior.

Additional context & links

Discovered by auditing sibling copy/state-preservation logic; no existing issue or PR covers this. .with_options() is a common pattern (per-request timeouts/headers), so Azure users combining it with azure_deployment are likely to hit this.

AzureOpenAI.copy()/with_options() reconstructs the client from base_url
via the base OpenAI.copy(), which does not pass azure_endpoint or
azure_deployment. As a result _azure_endpoint and _azure_deployment were
reset to None on the copy, so _prepare_url() no longer bypassed the
deployment path for non-deployment endpoints. A client built with
azure_deployment would then route e.g. /models to
/openai/deployments/<deployment>/models instead of /openai/models,
producing 404s after a copy.

Preserve _azure_endpoint/_azure_deployment on the copied client unless
the caller overrides base_url.
@hsusul
hsusul requested a review from a team as a code owner August 10, 2026 17:18

@ting-hong-shieh ting-hong-shieh left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Validated 59face1b on its original base and in a conflict-free synthetic merge onto current main at 721cb1cd; the latter includes the HTTPX2 migration that changed both files in this PR.

Using the same sync/async copy and with_options matrix, /models resolves outside the deployment path while /chat/completions retains /deployments/deployment-client. No Azure request or credentials were used.

Checks passed on the current-main merge:

  • pytest tests/lib/test_azure.py -q -n 0 — 63 passed
  • Ruff 0.14.7 lint and format checks
  • Pyright 1.1.399 and Mypy 1.17.0 on src/openai/lib/azure.py
  • git diff --check

I found no blocking issue.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-15T21:07:24.182896Z 77a34c6 New commits
🔒 Security Review ✅ Completed 2026-09-15T21:08:14.065730Z 77a34c6 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Castiron custom code

✅ No new custom-code files detected.

45 mixed files remain; 0 existing customizations changed.

Compared ccc10f595ff6 → 77a34c695075. Generated baselines verified.

45 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/realtime.py
  • 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

5 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 35023839241 --repo openai/openai-python \
  --name castiron-custom-code-35023839241-1 --dir /tmp/castiron-custom-code-35023839241-1
git apply --stat /tmp/castiron-custom-code-35023839241-1/custom-code.patch
cat /tmp/castiron-custom-code-35023839241-1/custom-code.patch

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

git fetch --no-tags origin ccc10f595ff606cf007459aecae6ddc90d02794a 77a34c695075269e2f79bc11144d324aa2252ec9
python3 scripts/castiron/custom_code_report.py report \
  --base ccc10f595ff606cf007459aecae6ddc90d02794a \
  --head 77a34c695075269e2f79bc11144d324aa2252ec9 --fetch --require-head-hash --public \
  --out /tmp/castiron-custom-code-77a34c695075
cat /tmp/castiron-custom-code-77a34c695075/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

@marcuswood-oai
marcuswood-oai added this pull request to the merge queue Sep 15, 2026
Merged via the queue into openai:main with commit b3c04f4 Sep 15, 2026
16 checks passed
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.

3 participants