Skip to content

fix(query): add regression tests for cross-task cancel-scope error during async-generator teardown#1009

Open
Oxygen56 wants to merge 1 commit into
anthropics:mainfrom
Oxygen56:fix/query-close-cancel-scope-983
Open

fix(query): add regression tests for cross-task cancel-scope error during async-generator teardown#1009
Oxygen56 wants to merge 1 commit into
anthropics:mainfrom
Oxygen56:fix/query-close-cancel-scope-983

Conversation

@Oxygen56
Copy link
Copy Markdown

Summary

Closes #983.

Background

Query.close() previously used anyio.TaskGroup which raised RuntimeError("Attempted to exit cancel scope in a different task") when close() was called from a different task during async-generator teardown (GeneratorExit). This occurred because anyio cancel scopes have task affinity: __aenter__ and __aexit__ must run on the same task.

The root cause was fixed by earlier PRs:

This PR

Adds targeted regression tests that reproduce the exact pattern from issue #983:

  • An async generator wrapping the message stream calls close() in its finally block, matching the pattern in _process_query_inner
  • The consumer breaks the loop, triggering GeneratorExit which runs the finally block on a (potentially) different task
  • Verifies that no RuntimeError is raised on either asyncio or trio backends

Testing

```
tests/test_query.py::TestQueryCrossTaskCleanup::test_close_from_different_task_does_not_raise PASSED
tests/test_query.py::TestQueryCrossTaskCleanup::test_close_from_same_task_still_works PASSED
tests/test_query.py::TestQueryCrossTaskCleanup::test_close_during_generator_teardown_asyncio PASSED
tests/test_query.py::TestQueryCrossTaskCleanup::test_close_during_generator_teardown_trio PASSED
```

All 47 tests in `test_task_compat.py` and `test_query.py` pass.

…scope error

Query.close() previously used anyio.TaskGroup which raised
RuntimeError("Attempted to exit cancel scope in a different task")
when close() was called from a different task during async-generator
teardown (GeneratorExit). This was fixed by PRs anthropics#746 and anthropics#870, which
replaced the task group with backend-agnostic spawn_detached()
from _task_compat.py.

This PR adds targeted regression tests that reproduce the exact
pattern from the issue:
- An async generator wrapping the message stream calls close() in
  its `finally` block, matching the pattern in _process_query_inner
- The consumer breaks the loop, triggering GeneratorExit which
  runs the finally block on a (potentially) different task
- Verifies that no RuntimeError is raised on either backend

Closes anthropics#983.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Query.close() raises 'Attempted to exit cancel scope in a different task' during async-generator teardown

1 participant