openai/openai-python · error · ValueError

The `default_headers` and `set_default_headers` arguments ar

Error message

The `default_headers` and `set_default_headers` arguments are mutually exclusive

What it means

`client.copy()` accepts either `default_headers` (merged over the existing headers) or `set_default_headers` (replaces them) — passing both is ambiguous, so it raises ValueError before creating the clone.

Source

Thrown at src/openai/_client.py:706

        websocket_base_url: str | httpx2.URL | None = None,
        base_url: str | httpx2.URL | None | NotGiven = not_given,
        data_residency: DataResidency | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx2.Client | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _enforce_credentials: bool | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        `data_residency` replaces the inherited HTTP and WebSocket endpoints, without changing this client.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        provider_changed = not isinstance(provider, NotGiven) and provider is not self._provider
        inherited_organization = None if provider_changed else self.organization
        inherited_project = None if provider_changed else self.project

        headers: Mapping[str, str] = {} if provider_changed else self._custom_headers
        if (
            is_x509_workload_identity(workload_identity)
            and not is_x509_workload_identity(self.workload_identity)
            and self._ambient_authorizations
        ):
            headers = {
                name: value
                for name, value in headers.items()
                if name.lower() != "authorization" or value not in self._ambient_authorizations

View on GitHub (pinned to 9917c6e28e)

Solutions

  1. Choose one: `copy(default_headers={'X-Extra': v'})` to merge, or `copy(set_default_headers={...})` to replace
  2. In shared helpers, resolve to a single parameter before calling copy

Example fix

# before
new = client.copy(default_headers={'X-A': '1'}, set_default_headers={'X-B': '2'})

# after
new = client.copy(set_default_headers={'X-A': '1', 'X-B': '2'})
Defensive patterns

Strategy: validation

Validate before calling

assert not (default_headers is not None and set_default_headers is not None), \
    'copy(): default_headers XOR set_default_headers'

Prevention

When it happens

Trigger: `client.copy(default_headers={...}, set_default_headers={...})`; wrapper/clone helpers forwarding both kwargs from callers.

Common situations: Config-layer code building derived clients that forwards any header kwargs it received; refactor from one parameter to the other leaving both populated.

Related errors


AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28). Data as JSON: /api/errors/c45f9691d4c566ad. Report an issue: GitHub.