Skip to content

fix: add logging to empty exception handlers in client destructors#3444

Open
C1-BA-B1-F3 wants to merge 5 commits into
openai:mainfrom
C1-BA-B1-F3:fix/empty-exception-handler-logging
Open

fix: add logging to empty exception handlers in client destructors#3444
C1-BA-B1-F3 wants to merge 5 commits into
openai:mainfrom
C1-BA-B1-F3:fix/empty-exception-handler-logging

Conversation

@C1-BA-B1-F3

Copy link
Copy Markdown

Summary

This PR addresses issue #3428 by replacing the empty except: pass exception handlers in SyncHttpxClientWrapper.__del__ and AsyncHttpxClientWrapper.__del__ with proper debug logging.

Changes

src/openai/_base_client.py:

  • SyncHttpxClientWrapper.__del__: Changed except Exception: pass to except Exception: log.debug("Failed to close client in destructor", exc_info=True)
  • AsyncHttpxClientWrapper.__del__: Changed except Exception: pass to except Exception: log.debug("Failed to close async client in destructor", exc_info=True)

tests/test_destructor_logging.py (new):

  • Tests that verify the sync/async client destructors log exceptions when close()/aclose() fails
  • Tests that verify no logging occurs on successful close
  • Tests that verify the destructor skips close if the client is already closed

Why This Approach

  • Debug level: The logging uses DEBUG level because destructor failures are typically not critical (the client is being garbage collected)
  • exc_info=True: Preserves the full traceback for debugging purposes
  • Non-breaking: The behavior is functionally identical from the caller's perspective — exceptions are still not raised from __del__
  • Uses existing logger: Uses the log logger already defined at module level

Fixes #3428

C1-BA-B1-F3 and others added 4 commits June 26, 2026 03:15
When a backend sends 'response.output: null' in a response.completed
event (e.g., chatgpt.com Codex backend), parse_response would crash
with TypeError: 'NoneType' object is not iterable.

This one-line fix coerces None to an empty list, allowing the stream
to complete gracefully. Consumers that track output_item.done events
can still backfill from their collected items.

Fixes openai#3325
After receiving the [DONE] SSE event, Stream.__stream__ and
AsyncStream.__stream__ now drain remaining events from the iterator
before calling response.close(). This ensures the HTTP/1.1 chunked
transfer-encoding terminator (0\r\n\r\n) is consumed, allowing h11's
their_state to advance to DONE so httpcore returns the connection to
the pool gracefully instead of destroying it with TCP FIN.

Regression from 6132922 which removed the drain.

Fixes openai#3440
…nnotation

The annotation field was typed as 'object' instead of the proper 'Annotation' union type (AnnotationFileCitation | AnnotationURLCitation | AnnotationContainerFileCitation | AnnotationFilePath). This forced consumers to use typing.cast to access typed fields.

Fixes openai#3419

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Previously, the __del__ methods of SyncHttpxClientWrapper and
AsyncHttpxClientWrapper silently swallowed all exceptions with bare
'except: pass' patterns, making debugging difficult.

Now these handlers log the exception at DEBUG level using the existing
logger, preserving the original behavior of not raising in destructors
while providing visibility into failures.

Fixes openai#3428
@C1-BA-B1-F3 C1-BA-B1-F3 requested a review from a team as a code owner June 25, 2026 21:01

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b89b1e38e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openai/_streaming.py Outdated
Comment on lines 115 to 117
for _ in iterator:
pass
response.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep closing the response if draining fails

If the cleanup drain raises while reading the remainder of the stream (for example a ReadTimeout/ReadError from a proxy or server before EOF), execution exits this finally block before response.close() runs. That regresses the previous guarantee that the response is released, and can also turn a stream that already delivered [DONE] into a cleanup-time read failure; wrap the drain so close still runs in a nested finally (the async drain below has the same pattern).

Useful? React with 👍 / 👎.

Comment thread tests/test_annotation_event.py Outdated
"""Test that the annotation field accepts all annotation types."""
# Verify the field is typed as Annotation (Union of all annotation types), not object
# We check by inspecting the model's field info directly
field_info = ResponseOutputTextAnnotationAddedEvent.model_fields["annotation"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the Pydantic compatibility helper

This test uses the Pydantic v2-only model_fields attribute, but the repo still supports and runs a test-pydantic-v1 nox session that installs pydantic<2; under that job ResponseOutputTextAnnotationAddedEvent only has __fields__, so this new test fails before checking the annotation type. Use openai._compat.get_model_fields(...) as the existing tests do for cross-version field inspection.

Useful? React with 👍 / 👎.

- Wrap stream drain in nested finally so response.close() always runs even if drain raises (sync + async)
- Use openai._compat.get_model_fields() instead of model_fields for Pydantic v1 compatibility in tests
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.

Empty exception handler in _base_client.py

1 participant