Skip to content

fix(exceptions): coerce APIError.code to str to match Optional[str] annotation - #3781

Open
okxint wants to merge 2 commits into
openai:mainfrom
okxint:fix/api-status-error-code-type
Open

fix(exceptions): coerce APIError.code to str to match Optional[str] annotation#3781
okxint wants to merge 2 commits into
openai:mainfrom
okxint:fix/api-status-error-code-type

Conversation

@okxint

@okxint okxint commented Sep 2, 2026

Copy link
Copy Markdown

Summary

APIError.code is typed Optional[str] but could silently hold an int at runtime when the API returns a numeric error code.

Root cause:

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

construct_type returns the raw value unchanged when the runtime type doesn't match the target type. Wrapping with cast(Any, ...) suppresses the type checker, so a numeric code (e.g. 1001) escapes into .code as an int. Any downstream code that treats .code as a string (.strip(), .startswith(), string comparison) will raise AttributeError: 'int' object has no attribute 'strip'.

Fix:

raw_code = body.get("code")
self.code = str(raw_code) if raw_code is not None else None

Explicit str() coercion guarantees the annotation is always honoured regardless of what the API sends.

Checklist

  • Fix matches Optional[str] annotation on APIError.code
  • None is preserved when code key is absent or null
  • No behaviour change for APIs that already send string codes
  • Removes the cast(Any, ...) type-suppression workaround

…nnotation

APIError.code is annotated Optional[str], but the OpenAI API (and
compatible gateways) can return a JSON number in the error.code field.
construct_type returns non-matching values unchanged, and the cast(Any,…)
suppressed type-checker warnings, so integer codes silently escaped as
int at runtime — crashing downstream code that trusted the annotation
(e.g. exc.code.strip() raises AttributeError).

Fix: replace the construct_type call with an explicit coercion:

  raw_code = body.get("code")
  self.code = str(raw_code) if raw_code is not None else None

This keeps the annotation correct (Optional[str]), is backward-compatible
for callers that already receive string codes, and converts integer codes
(404, 429, etc.) to their string representations.

Fixes openai#3531

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@okxint
okxint requested a review from a team as a code owner September 2, 2026 06:56
dajiaohuang added a commit to dajiaohuang/openai-python that referenced this pull request Sep 2, 2026
…nnotation

Replace construct_type call with explicit str coercion to properly handle
numeric code values returned by the API.

Fixes openai#3781

@sylvesterkaczmarek sylvesterkaczmarek 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.

This is the same runtime fix already open in #3611, but this version drops the regression that proves numeric codes are normalised. I'd either close this in favour of #3611 or carry over a focused int/string/None test. Otherwise the exact construct_type mismatch that motivated the change isn't pinned by CI.

@okxint

okxint commented Sep 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review @sylvesterkaczmarek. Added a focused regression in the latest commit that pins int→str, str→str, and None→None coercion under CI. Happy to close in favor of #3611 if the maintainers prefer — but since #3611 still shows a formatting failure I figured it's worth keeping this one moving.

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