Description
When displaying a chained exception where only the outermost exception has __notes__ (via PEP 678 add_note), Rich incorrectly displays those notes on every exception in the chain, not just the one that has them. Investigation done by Claude.
Minimal reproduction
from rich.console import Console
try:
try:
raise ValueError("original error")
except ValueError as exc:
raise RuntimeError("wrapped error") from exc
except RuntimeError as exc:
exc.add_note("This note was added to RuntimeError only")
Console(width=80).print_exception()
Rich output (note appears on both exceptions):
ValueError: original error
[NOTE] This note was added to RuntimeError only <-- should not be here
The above exception was the direct cause of the following exception:
RuntimeError: wrapped error
[NOTE] This note was added to RuntimeError only <-- correct
Python native output (note appears only on RuntimeError, as expected):
ValueError: original error
The above exception was the direct cause of the following exception:
RuntimeError: wrapped error
This note was added to RuntimeError only
Cause
In Traceback.extract(), the notes are read once from the initial exc_value before the while True loop, then the same list is reused for every Stack in the chain:
notes: List[str] = getattr(exc_value, "__notes__", None) or [] # read once
while True:
stack = Stack(
...
notes=notes, # same reference for ALL stacks
)
# ... walks __cause__ / __context__ chain ...
The fix would be to read notes from each exc_value inside the loop:
while True:
notes: List[str] = getattr(exc_value, "__notes__", None) or []
stack = Stack(
...
notes=notes,
)
Environment
Description
When displaying a chained exception where only the outermost exception has
__notes__(via PEP 678add_note), Rich incorrectly displays those notes on every exception in the chain, not just the one that has them. Investigation done by Claude.Minimal reproduction
Rich output (note appears on both exceptions):
Python native output (note appears only on RuntimeError, as expected):
Cause
In
Traceback.extract(), thenotesare read once from the initialexc_valuebefore thewhile Trueloop, then the same list is reused for everyStackin the chain:The fix would be to read
notesfrom eachexc_valueinside the loop:Environment