Skip to content

Commit 51e3166

Browse files
worksbyfridayclaude
andcommitted
Fix exceptions raised before task_status.started() losing cause and context
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 #3261. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6deaf2a commit 51e3166

File tree

2 files changed

+22
-2
lines changed

2 files changed

+22
-2
lines changed

src/trio/_core/_run.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from .. import _core
3434
from .._abc import Clock, Instrument
3535
from .._deprecate import warn_deprecated
36-
from .._util import NoPublicConstructor, coroutine_or_error, final
36+
from .._util import NoPublicConstructor, coroutine_or_error, final, raise_saving_context
3737
from ._asyncgens import AsyncGenerators
3838
from ._concat_tb import concat_tb
3939
from ._entry_queue import EntryQueue, TrioToken
@@ -1466,7 +1466,7 @@ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
14661466
# cancel this nursery:
14671467
except BaseExceptionGroup as exc:
14681468
if len(exc.exceptions) == 1:
1469-
raise exc.exceptions[0] from None
1469+
raise_saving_context(exc.exceptions[0])
14701470
raise TrioInternalError(
14711471
"Internal nursery should not have multiple tasks. This can be "
14721472
'caused by the user managing to access the "old" nursery in '

src/trio/_core/_tests/test_run.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,26 @@ async def start_raiser() -> None:
29202920
assert should_be_raiser_exc.exceptions == raiser_exc.exceptions
29212921

29222922

2923+
async def test_start_exception_preserves_cause_and_context() -> None:
2924+
"""Regression test for #3261: exceptions raised before task_status.started()
2925+
should preserve __cause__ and __context__."""
2926+
2927+
async def task(*, task_status: _core.TaskStatus[None]) -> None:
2928+
e = ValueError("foo")
2929+
e.__cause__ = SyntaxError("bar")
2930+
e.__context__ = TypeError("baz")
2931+
raise e
2932+
2933+
with pytest.raises(BaseExceptionGroup) as exc_info:
2934+
async with _core.open_nursery() as nursery:
2935+
await nursery.start(task)
2936+
assert len(exc_info.value.exceptions) == 1
2937+
exc = exc_info.value.exceptions[0]
2938+
assert isinstance(exc, ValueError)
2939+
assert isinstance(exc.__cause__, SyntaxError)
2940+
assert isinstance(exc.__context__, TypeError)
2941+
2942+
29232943
async def test_internal_error_old_nursery_multiple_tasks() -> None:
29242944
async def error_func() -> None:
29252945
raise ValueError

0 commit comments

Comments
 (0)