Skip to content

fix: coerce API error code/param/type to str - #3759

Open
Manny7717 wants to merge 1 commit into
openai:mainfrom
Manny7717:fix/error-code-str-coercion
Open

fix: coerce API error code/param/type to str#3759
Manny7717 wants to merge 1 commit into
openai:mainfrom
Manny7717:fix/error-code-str-coercion

Conversation

@Manny7717

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

Changes being requested

Fixes #3531APIStatusError.code is annotated Optional[str] but can be an int at runtime.

Problem: APIError.__init__ builds code/param/type with the lenient deserializer:

self.code = cast(Any, construct_type(type_=Optional[str], value=body.get("code")))

construct_type neither coerces nor rejects values that don't match the target type — it returns them unchanged — and the cast(Any, ...) suppresses the type checker. So when a server sends an integer error code (observed from api.openai.com under load, and routinely from OpenAI-compatible gateways), exc.code is an int while the annotation promises str | None. Downstream code that trusts the annotation (e.g. exc.code.strip()) crashes with AttributeError after the API failure already happened.

Fix: coerce non-None values to str in the exception constructor, so the runtime value always matches the documented annotation. The raw value remains available on APIError.body for consumers that need it. Applied consistently to code, param and type (all three share the same pattern).

Tests: added test_api_status_error_code_is_coerced_to_str to both the sync and async client suites (end-to-end via a mocked 400 response with "code": 404). Verified revert-proven: both new tests fail on the pre-fix code and pass with the fix. Full tests/test_client.py: 200 passed, 2 skipped. ruff check . and mypy src/openai/_exceptions.py clean.

Note: src/openai/_exceptions.py is handwritten — it carries no Castiron "File generated from our OpenAPI spec" marker — so this change won't be overwritten by the generator.

Additional context & links

APIError.code is annotated Optional[str] but construct_type is a lenient
deserializer: it passes non-str values through unchanged, and the
cast(Any, ...) silenced the type checker. Servers (api.openai.com under
load, and OpenAI-compatible gateways) can send integer error codes, so
exc.code could be an int at runtime and crash downstream consumers doing
exc.code.strip().

Coerce non-None values to str in the exception constructor so the runtime
value always matches the annotation. The raw value stays available on
APIError.body. Fixes openai#3531.
@Manny7717
Manny7717 requested a review from a team as a code owner August 29, 2026 05:26
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.

APIStatusError.code is typed Optional[str] but can be an int at runtime

1 participant