Fix exception cause/context lost before task_status.started()#3394
Fix exception cause/context lost before task_status.started()#3394A5rocks merged 3 commits intopython-trio:mainfrom
Conversation
…ontext When a task raised an exception before calling task_status.started(), Nursery.start() unwrapped it from the internal ExceptionGroup using `raise exc.exceptions[0] from None`, which explicitly set __cause__ to None and __suppress_context__ to True — destroying any cause or context the original exception carried. Use raise_saving_context() instead, which preserves both __cause__ and __context__ on the re-raised exception, consistent with how raise_single_exception_from_group() handles the same pattern. Fixes python-trio#3261. Co-Authored-By: Claude Opus 4.6 <[email protected]>
A5rocks
left a comment
There was a problem hiding this comment.
Sure whatever go go gadget Claude.
(As a note to humans I'd rather PRs from people familiar with the code base (even using a coding agent!) rather than this kinda thing. But this is simple enough + is literally just what @jakkdl proposed so this seems fine.)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3394 +/- ##
===============================================
Coverage 100.00000% 100.00000%
===============================================
Files 128 128
Lines 19415 19424 +9
Branches 1318 1318
===============================================
+ Hits 19415 19424 +9
🚀 New features to boost your workflow:
|
Co-Authored-By: Claude Opus 4.6 <[email protected]>
src/trio/_core/_tests/test_run.py
Outdated
There was a problem hiding this comment.
| with pytest.raises(BaseExceptionGroup) as exc_info: | |
| async with _core.open_nursery() as nursery: | |
| await nursery.start(task) | |
| assert len(exc_info.value.exceptions) == 1 | |
| exc = exc_info.value.exceptions[0] | |
| assert isinstance(exc, ValueError) | |
| assert isinstance(exc.__cause__, SyntaxError) | |
| assert isinstance(exc.__context__, TypeError) | |
| with pytest.RaisesGroup( | |
| pytest.RaisesExc(ValueError, | |
| check=lambda exc: isinstance(exc.__cause__, SyntaxError) and isinstance(exc.__context__, TypeError) | |
| ) | |
| ) as exc_info: | |
| async with _core.open_nursery() as nursery: | |
| await nursery.start(task) |
not 100% sure that putting the exc.__cause__ and exc.__context__ checks inside RaisesExc is better than outside, but it should at least use RaisesGroup.
There was a problem hiding this comment.
this is making me think there should be a lint rule that checks for pytest.raises([Base]ExceptionGroup) - created python-trio/flake8-async#430
Address review feedback: use trio's pytest.RaisesGroup helper for cleaner exception group assertions. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Updated to use |
|
Hey @Fridayai700, it looks like that was the first time we merged one of your PRs! Thanks so much! 🎉 🎂 If you want to keep contributing, we'd love to have you. So, I just sent you an invitation to join the python-trio organization on Github! If you accept, then here's what will happen:
If you want to read more, here's the relevant section in our contributing guide. Alternatively, you're free to decline or ignore the invitation. You'll still be able to contribute as much or as little as you like, and I won't hassle you about joining again. But if you ever change your mind, just let us know and we'll send another invitation. We'd love to have you, but more importantly we want you to do whatever's best for you. If you have any questions, well... I am just a humble Python script, so I probably can't help. But please do post a comment here, or in our chat, or on our forum, whatever's easiest, and someone will help you out! |
Summary
Fixes #3261.
When a task raises an exception before calling
task_status.started(),Nursery.start()unwraps it from the internalExceptionGroupusing:The
from Noneexplicitly sets__cause__toNoneand__suppress_context__toTrue, destroying any cause or context the original exception carried.Fix
Replace the bare
raise ... from Nonewithraise_saving_context(), which preserves both__cause__and__context__. This is the same utility already used byraise_single_exception_from_group()for the same pattern.Before
After
Test plan
test_start_exception_preserves_cause_and_contexttest_run.pytests pass