Skip to content

Fix exception cause/context lost before task_status.started()#3394

Merged
A5rocks merged 3 commits intopython-trio:mainfrom
Fridayai700:fix-start-exception-context
Feb 18, 2026
Merged

Fix exception cause/context lost before task_status.started()#3394
A5rocks merged 3 commits intopython-trio:mainfrom
Fridayai700:fix-start-exception-context

Conversation

@Fridayai700
Copy link
Contributor

Summary

Fixes #3261.

When a task raises an exception before calling task_status.started(), Nursery.start() unwraps it from the internal ExceptionGroup using:

raise exc.exceptions[0] from None

The from None explicitly sets __cause__ to None and __suppress_context__ to True, destroying any cause or context the original exception carried.

Fix

Replace the bare raise ... from None with raise_saving_context(), which preserves both __cause__ and __context__. This is the same utility already used by raise_single_exception_from_group() for the same pattern.

Before

>>> excgroup.exceptions[0].__cause__   # should be SyntaxError, was None
>>> excgroup.exceptions[0].__context__ # should be TypeError, was the ExceptionGroup

After

>>> excgroup.exceptions[0].__cause__   # SyntaxError("bar")
>>> excgroup.exceptions[0].__context__ # TypeError("baz")

Test plan

  • New regression test test_start_exception_preserves_cause_and_context
  • All 28 start-related tests pass
  • All 154 test_run.py tests pass

…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]>
Copy link
Contributor

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

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
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00000%. Comparing base (6deaf2a) to head (48aadaf).
⚠️ Report is 1 commits behind head on main.

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     
Files with missing lines Coverage Δ
src/trio/_core/_run.py 100.00000% <100.00000%> (ø)
src/trio/_core/_tests/test_run.py 100.00000% <100.00000%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines 2933 to 2940
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Member

@jakkdl jakkdl Feb 18, 2026

Choose a reason for hiding this comment

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

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]>
@Fridayai700
Copy link
Contributor Author

Updated to use pytest.RaisesGroup with pytest.RaisesExc as suggested. Much cleaner — thanks for the review!

@A5rocks A5rocks merged commit 2fd138e into python-trio:main Feb 18, 2026
44 checks passed
@trio-bot
Copy link

trio-bot bot commented Feb 18, 2026

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:

  • Github will automatically subscribe you to notifications on all our repositories. (But you can unsubscribe again if you don't want the spam.)

  • You'll be able to help us manage issues (add labels, close them, etc.)

  • You'll be able to review and merge other people's pull requests

  • You'll get a [member] badge next to your name when participating in the Trio repos, and you'll have the option of adding your name to our member's page and putting our icon on your Github profile (details)

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!

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.

exceptions raised before calling task_status.started loses cause & context

4 participants

Comments