Skip to content

Conversation

@gn00295120
Copy link
Contributor

Summary

Fixes resource leak in OpenAISTTTranscriptionSession where tasks are cancelled but not properly awaited during cleanup.

Problem

The _cleanup_tasks() method in OpenAISTTTranscriptionSession was only calling task.cancel() on pending tasks (listener, process_events, stream_audio, connection) but not awaiting them. This could lead to:

  1. Unhandled task exception warnings
  2. Potential resource leaks (websocket connections, file descriptors)
  3. Improper cleanup of background tasks

Evidence

Solution

  1. Made _cleanup_tasks() async
  2. Collect all real asyncio.Task objects that need to be awaited
  3. Added await asyncio.gather() with return_exceptions=True to properly collect exceptions from cancelled tasks
  4. Updated close() method to await _cleanup_tasks()

Testing

Files Changed

  • src/agents/voice/models/openai_stt.py: Update task cleanup to properly await cancelled tasks

@Copilot Copilot AI review requested due to automatic review settings October 22, 2025 16:55
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes a resource leak in OpenAISTTTranscriptionSession by ensuring cancelled tasks are properly awaited during cleanup. Previously, tasks were cancelled but not awaited, which could lead to unhandled task exception warnings and potential resource leaks.

Key Changes:

  • Made _cleanup_tasks() async and added proper awaiting of cancelled tasks using asyncio.gather()
  • Added validation in Agent.__post_init__() to check that each tool in the tools list is a valid Tool object
  • Added comprehensive test coverage for the new tool validation

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/agents/voice/models/openai_stt.py Made _cleanup_tasks() async and added proper awaiting of cancelled tasks to prevent resource leaks
src/agents/agent.py Added tool type validation in __post_init__() to raise UserError for invalid tool objects
tests/test_agent_config.py Added test cases for tool validation covering raw functions, strings, and mixed valid/invalid tools

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@gn00295120 gn00295120 force-pushed the fix-voice-stt-task-cleanup branch from 10c275c to de5bd08 Compare October 22, 2025 16:57
@gn00295120
Copy link
Contributor Author

@codex review

gn00295120 pushed a commit to gn00295120/openai-agents-python that referenced this pull request Oct 22, 2025
…asks

Problem:
The _cleanup_tasks() method in VoiceStreamResult was only calling
task.cancel() on pending tasks but not awaiting them. Additionally,
_check_errors() could raise CancelledError when checking cancelled tasks.
This could lead to:
1. Unhandled task exception warnings
2. Potential resource leaks from abandoned tasks
3. CancelledError masking real exceptions

Evidence:
- Similar to fixed guardrail tasks cleanup (PR openai#1976)
- Similar to fixed voice STT cleanup (PR openai#1977)
- Similar to fixed websocket cleanup (PR openai#1955)
- Bug documented in .claude/bug-analysis/03-resource-leaks.md

Solution:
1. Made _cleanup_tasks() async
2. Collect all real asyncio.Task objects that need to be awaited
3. Added await asyncio.gather() with return_exceptions=True to properly
   collect exceptions from cancelled tasks
4. Updated _check_errors() to skip cancelled tasks using task.cancelled()
   check to avoid CancelledError when calling task.exception()
5. Updated stream() async generator to await _cleanup_tasks()

Testing:
- Linting passes
- No breaking changes to public API
- Follows same pattern as PR openai#1976, openai#1977, openai#1955
@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. Breezy!

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


if self._listener_task and not self._listener_task.done():
self._listener_task.cancel()
if isinstance(self._listener_task, asyncio.Task):
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps, this is due to the same reason with #1976 but we don't want to have this check, which is unnecessary for main code

Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

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

any chance to add unit tests for this code path?

Lucas Wang added 2 commits October 24, 2025 09:49
Problem:
The _cleanup_tasks() method in OpenAISTTTranscriptionSession was only calling
task.cancel() on pending tasks (listener, process_events, stream_audio, connection)
but not awaiting them. This could lead to:
1. Unhandled task exception warnings
2. Potential resource leaks (websocket connections, file descriptors)
3. Improper cleanup of background tasks

Evidence:
- Similar to recently fixed guardrail tasks cleanup (PR openai#1976)
- Similar to fixed websocket task cleanup (PR openai#1955)
- asyncio best practices require awaiting cancelled tasks

Solution:
1. Made _cleanup_tasks() async
2. Collect all real asyncio.Task objects that need to be awaited
3. Added await asyncio.gather() with return_exceptions=True to properly
   collect exceptions from cancelled tasks
4. Updated close() method to await _cleanup_tasks()

Testing:
- All existing voice/STT tests pass (17 passed)
- Uses isinstance check to support mock objects in tests
- Follows the same pattern as PR openai#1976 and PR openai#1955
Simplified the cleanup logic by removing isinstance checks for
asyncio.Task. All tasks are created through asyncio.create_task(),
so these type checks are unnecessary.

This makes the code cleaner and avoids test-specific workarounds
in production code, following the same pattern as PR openai#1976.
@gn00295120 gn00295120 force-pushed the fix-voice-stt-task-cleanup branch from caaefe8 to 5a3b758 Compare October 24, 2025 01:49
- Test cancellation and awaiting of all pending tasks
- Test exception handling with return_exceptions=True
- Test close() properly calls cleanup_tasks
- All tests pass and verify proper resource cleanup
@gn00295120 gn00295120 force-pushed the fix-voice-stt-task-cleanup branch from 5a3b758 to 6ebc71e Compare October 24, 2025 01:52
@gn00295120
Copy link
Contributor Author

any chance to add unit tests for this code path?

✅ Added 3 unit tests:
test_cleanup_tasks_cancels_and_awaits_all_tasks – tests canceling and awaiting all pending tasks.
test_cleanup_tasks_handles_exceptions – tests handling exceptions with return_exceptions=True.
test_close_calls_cleanup_tasks – tests that close() correctly calls cleanup_tasks().

These tests cover _cleanup_tasks() and verify that:
Tasks are properly canceled and awaited.
No exceptions are raised (thanks to return_exceptions).
The close() method correctly invokes the cleanup logic.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants