Skip to content

Empty OPENAI_BASE_URL prevents fallback to default API endpoint #2927

Open

Description

@thelyad

Confirm this is an issue with the Python library and not an underlying OpenAI API

  • This is an issue with the Python library

Describe the bug

I noticed a small issue in the base_url initialization logic: (

if base_url is None:
base_url = os.environ.get("OPENAI_BASE_URL")
if base_url is None:
base_url = f"https://api.openai.com/v1"
)

if base_url is None:
    base_url = os.environ.get("OPENAI_BASE_URL")
if base_url is None:
    base_url = f"https://api.openai.com/v1"

If the environment variable OPENAI_BASE_URL is defined but empty (e.g., OPENAI_BASE_URL=""), then os.environ.get("OPENAI_BASE_URL") returns an empty string. As a result:

  • base_url becomes ""
  • the second condition (if base_url is None) is never triggered
  • the intended fallback "https://api.openai.com/v1" is skipped

An empty string is not a valid base URL, so this can lead to confusing behavior.

To Reproduce

  1. export OPENAI_BASE_URL=""
  2. run the following:
from openai import OpenAI

client = OpenAI()
client.models.list()
  1. Trigger an "openai.APIConnectionError: Connection error" error

Code snippets

OS

macOS

Python version

Python 3.12.2

Library version

2.15.0

Activity

  1. added a commit that references this issue on Mar 4, 2026
    563e8ae
  2. added a commit that references this issue on Mar 7, 2026
    0998d8f
  3. xXMrNidaXx commented on Mar 8, 2026

    @xXMrNidaXx

    Good catch on the empty string edge case. The current logic:

    if base_url is None:
        base_url = os.environ.get('OPENAI_BASE_URL')
    if base_url is None:
        base_url = f'https://api.openai.com/v1'

    ...uses is None for both checks, but os.environ.get() returns an empty string '' (not None) when the variable is set but empty. Empty string is falsy but not None, so the fallback never triggers.

    The fix should be:

    if base_url is None:
        base_url = os.environ.get('OPENAI_BASE_URL') or None  # converts '' to None
    if base_url is None:
        base_url = 'https://api.openai.com/v1'

    Or equivalently:

    base_url = base_url or os.environ.get('OPENAI_BASE_URL') or 'https://api.openai.com/v1'

    Workaround: Unset the environment variable entirely (unset OPENAI_BASE_URL or del os.environ['OPENAI_BASE_URL']) rather than setting it to an empty string. The bug only triggers when OPENAI_BASE_URL is defined but empty — if it's not set at all, os.environ.get() returns None and the fallback works correctly.

  4. giulio-leone commented on Mar 9, 2026

    @giulio-leone

    This is addressed by #2939 — when OPENAI_BASE_URL is set to an empty string, it now falls back to the default API endpoint instead of constructing an invalid URL.

  5. added a commit that references this issue on Mar 10, 2026
    18fe6fa
  6. mvanhorn commented on Mar 10, 2026

    @mvanhorn

    I've submitted a fix for this in PR #2952. Uses or None to normalize empty env var strings, applied to both sync and async constructors.

  7. passionworkeer commented on Mar 14, 2026

    @passionworkeer

    This is a simple fix! I can submit a PR to handle the empty string case for OPENAI_BASE_URL.

  8. added 2 commits that reference this issue on Mar 15, 2026
    38e4fa2
    416ce55
  9. theamodhshetty commented on Mar 19, 2026

    @theamodhshetty

    Opened #2988 for this.

    The change only touches the environment fallback path: if OPENAI_BASE_URL is set to an empty string, the client now treats it the same as an unset value and falls back to the default API base URL. Explicit base_url arguments are left alone. I also added sync and async regressions for the empty-env case.

  10. added a commit that references this issue on Mar 20, 2026
    3717560
  11. added a commit that references this issue on Mar 21, 2026
    17b4fdb
  12. added a commit that references this issue on Mar 22, 2026
    221da82
  13. added a commit that references this issue on Mar 24, 2026
    c499207
  14. khalidsaidi commented on Mar 25, 2026

    @khalidsaidi

    A2ABench has an accepted answer for this imported thread.

  15. added a commit that references this issue on Apr 2, 2026
    c408851
  16. dajiaohuang commented on Sep 25, 2026

    @dajiaohuang

    Proposed Comprehensive Fix

    I can confirm this bug affects both sync and async clients (src/openai/_client.py) and applies to not just OPENAI_BASE_URL but also potentially other empty-string environment variables. The Bedrock provider (src/openai/providers/bedrock.py) already handles this correctly using a _normalize_optional_string() helper.

    Affected Locations (4 places in _client.py):

    1. Sync client - line 306: base_url = os.environ.get("OPENAI_BASE_URL")
    2. Async client - line 1066: base_url = os.environ.get("OPENAI_BASE_URL")
    3. The bug also affects self._base_url_was_default tracking (lines 307 and 1067):
      • Currently self._base_url_was_default = provider_runtime is None and base_url is None
      • This incorrectly marks "" as user-provided instead of falling through to default

    Proposed Fix Code:

    Add a shared helper function in src/openai/_utils/_utils.py (or at top of _client.py):

    def _get_env(name: str) -> str | None:
        """Get an environment variable, treating empty strings as unset."""
        value = os.environ.get(name)
        return value if value else None

    Then replace all occurrences in src/openai/_client.py:

    Line 305-310 (sync client):

    if provider_runtime is not None:
        base_url = provider_runtime.base_url
    elif base_url is None:
        base_url = _get_env("OPENAI_BASE_URL")
    self._base_url_was_default = provider_runtime is None and base_url is None
    self._data_residency = data_residency
    if base_url is None:
        base_url = MTLS_API_BASE_URL if x509_identity is not None else "https://api.openai.com/v1"

    Line 1065-1070 (async client):

    if provider_runtime is not None:
        base_url = provider_runtime.base_url
    elif base_url is None:
        base_url = _get_env("OPENAI_BASE_URL")
    self._base_url_was_default = provider_runtime is None and base_url is None
    self._data_residency = data_residency
    if base_url is None:
        base_url = MTLS_API_BASE_URL if x509_identity is not None else "https://api.openai.com/v1"

    This pattern matches what already exists in src/openai/providers/bedrock.py:30-35:

    def _normalize_optional_string(value: str | None) -> str | None:
        if value is None:
            return None
        value = value.strip()
        return value or None

    A more thorough fix would also apply this normalization to:

    • OPENAI_API_KEY (line 253, 1013)
    • OPENAI_ADMIN_KEY (line 263, 1023)
    • OPENAI_ORG_ID (line 279, 1039)
    • OPENAI_PROJECT_ID (line 283, 1043)
    • OPENAI_WEBHOOK_SECRET (line 287, 1047)
    • AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, etc. in lib/azure.py

    Using .strip() is also important for handling whitespace-only environment variable values which would also cause confusing failures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions