fix: Resolve RuntimeError on async generator cleanup#527
Draft
wingding12 wants to merge 1 commit intoanthropics:mainfrom
Draft
fix: Resolve RuntimeError on async generator cleanup#527wingding12 wants to merge 1 commit intoanthropics:mainfrom
wingding12 wants to merge 1 commit intoanthropics:mainfrom
Conversation
Fixes issue anthropics#454 where RuntimeError was raised during async generator cleanup. Problem: Task group __aenter__() was called in one task but __aexit__() was called in a different task during cleanup, which AnyIO doesn't allow. Solution: - Added CancelScope for reader task that can be cancelled from any context - Graceful __aexit__() handling that catches cross-task RuntimeError - Added aclosing() wrapper in process_query() for proper cleanup - Made Query an async context manager for cleaner usage Changes: - query.py: Added cancel scope mechanism and async context manager support - client.py: Added aclosing() and GeneratorExit handling - test_streaming_client.py: Added 3 tests for async cleanup scenarios All 134 tests pass.
brianantonelli
added a commit
to brianantonelli/claude-agent-sdk-python
that referenced
this pull request
Jan 28, 2026
This fixes the "Attempted to exit cancel scope in a different task than it was entered in" error that occurs when sequential query() calls are made. The fix uses two mechanisms: 1. A CancelScope for the reader task that can be cancelled from any context 2. Suppressing the RuntimeError that occurs when task group __aexit__() is called from a different task than __aenter__() Also adds aclosing() wrapper and GeneratorExit handling in process_query() for proper async generator cleanup. Based on PR anthropics#527: anthropics#527 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
phil65
added a commit
to phil65/claude-agent-sdk-python
that referenced
this pull request
Jan 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes issue #454 where a
RuntimeError: Attempted to exit cancel scope in a different task than it was entered inwas raised during async generator cleanup.Problem
The issue occurred when:
Query.start()called__aenter__()on the task group in task Aasync forloop, or whenasyncio.run()shuts down),Query.close()called__aexit__()in task BSolution
Added CancelScope for reader task - The reader task now has its own
CancelScopethat can be safely cancelled from any task context, without requiring the task group's__aexit__().Graceful
__aexit__()handling - Theclose()method now catches the RuntimeError when__aexit__()is called from a different task and handles it gracefully by logging and continuing cleanup.Added
aclosing()wrapper -process_query()now usescontextlib.aclosing()for better async generator cleanup.Query as async context manager - Added
__aenter__/__aexit__methods to Query for cleaner usage patterns.Changes
src/claude_agent_sdk/_internal/query.py:_reader_cancel_scopefor safe cross-task cancellation_read_messages_with_cancel_scope()wrapperclose()to handle cross-task cleanup gracefully__aenter__/__aexit__)src/claude_agent_sdk/_internal/client.py:aclosing()for proper async generator cleanuptests/test_streaming_client.py:TestAsyncGeneratorCleanuptest class with 3 testsTest Plan
Example
The issue was reproducible with:
After this fix, breaking out of the loop works cleanly without errors.
Fixes #454