You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Traceback rendering fails on ParseError with a non-string message #4225
Checked the traceback documentation and searched open/closed issues for SyntaxError, ParseError, and ExpatError.
Checked the FAQ.
Describe the bug
Rendering an xml.etree.ElementTree.ParseError whose message is an ExpatError raises a secondary TypeError, masking the original XML parse error. This occurs with the pure-Python ElementTree parser used by defusedxml (see tiran/defusedxml#105). The reproduction below needs only Rich and the standard library:
TypeError: str or Text instance required, not ExpatError('syntax error')
Expected: render the original exception without raising a new exception.
ParseError subclasses SyntaxError. Traceback.extract() currently stores exc_value.msg directly in _SyntaxError, although it already has a safe_str() helper for the main exception value. Would normalizing this field with that helper, with tests for non-string messages and a failing __str__, be an acceptable fix?
Platform
macOS arm64, CPython 3.13.5, Rich 15.0.0. Also reproduced using the current main checkout. Output is directed to StringIO, so no terminal-specific rendering is involved. The original end-to-end example also fails with defusedxml 0.8.0rc2 and structlog 26.1.0. Explicitly selecting structlog.dev.plain_traceback is a verified workaround for that integration.
This report and local reproduction were prepared with OpenAI Codex. Per the AI policy, no PR is being submitted without maintainer approval of the proposed solution.
"Confirmed and reproduced locally (rich 15.0.0, CPython 3.14): Console().print(Traceback.from_exception(*sys.exc_info())) under ParseError(ExpatError(...)) raises TypeError: str or Text instance required, not ExpatError("syntax error").
Root cause is a bit narrower than the title: the SyntaxError branch stores the raw message at rich/traceback.py:517 (msg=exc_value.msg, unnormalized), and render_stack feeds that field into ReprHighlighter at traceback.py:687, which only accepts str/Text. Normalizing at extraction is sufficient and minimal:
msg=safe_str(exc_value.msg),
safe_str already exists and is used for exc_value at line 486; for a standard SyntaxError the message is already a str, so this is a no-op.
I also tested the adjacent sub-cases: an embedded-newlines message renders fine (no crash), while None and other non-str messages crash exactly as above.
Per AI_POLICY.md an AI-assisted PR needs a maintainer to approve the solution on the issue first. Maintainers, would you be open to this one-line fix with a regression test alongside test_syntax_error in tests/test_traceback.py? Happy to submit once approved. @willmcgugan
(Note: this reproduction/analysis was prepared with AI assistance)"
For the record I encountered the bug using PyPy on this MRE:
importxml.etree.ElementTreeasetreefromrich.consoleimportConsoleconsole=Console()
try:
xml=etree.XML("<")
exceptExceptionase:
console.print_exception() # TypeError: str or Text instance required,# not ExpatError('unclosed token: line 1, column 0')
The reason is that in PyPy, the pure-Python version lxml sets the first argument of ParseError to a non-str, while in CPython, the C version of lxml casts it to a str.
I opened a CPython issue to fix this upstream (as PyPy use CPython stdlib), but I still believe rich should not fail here, as you generally do not want your error path to error itself.
But SyntaxError.msg is not guaranteed to be a str. xml.etree.ElementTree.ParseError (and xml.parsers.expat.ExpatError) carry exception-specific msg values — when a ParseError is constructed from an ExpatError instance, msg is the ExpatError object itself, not a string. Rich's highlighter() requires str, so rendering crashes with:
TypeError: str or Text instance required, not ExpatError('syntax error')
Added tests/test_traceback.py::test_syntax_error_non_str_message (raises a ParseError wrapping an ExpatError, prints the exception, asserts "ParseError" appears in the output). Passes locally; the traceback test suite is green with the change.
Ready-to-merge branch
Since PR creation on this repo is currently limited to collaborators, the complete fix (fix + regression test + CHANGELOG entry) is on my fork:
Activity
5h4d0wn1k commentedon Sep 23, 2026
"Confirmed and reproduced locally (rich 15.0.0, CPython 3.14): Console().print(Traceback.from_exception(*sys.exc_info())) under ParseError(ExpatError(...)) raises TypeError: str or Text instance required, not ExpatError("syntax error").
Root cause is a bit narrower than the title: the SyntaxError branch stores the raw message at rich/traceback.py:517 (msg=exc_value.msg, unnormalized), and render_stack feeds that field into ReprHighlighter at traceback.py:687, which only accepts str/Text. Normalizing at extraction is sufficient and minimal:
safe_str already exists and is used for exc_value at line 486; for a standard SyntaxError the message is already a str, so this is a no-op.
I also tested the adjacent sub-cases: an embedded-newlines message renders fine (no crash), while None and other non-str messages crash exactly as above.
Per AI_POLICY.md an AI-assisted PR needs a maintainer to approve the solution on the issue first. Maintainers, would you be open to this one-line fix with a regression test alongside test_syntax_error in tests/test_traceback.py? Happy to submit once approved. @willmcgugan
(Note: this reproduction/analysis was prepared with AI assistance)"
alexprengere commentedon Sep 25, 2026
For the record I encountered the bug using PyPy on this MRE:
The reason is that in PyPy, the pure-Python version
lxmlsets the first argument ofParseErrorto a non-str, while in CPython, the C version oflxmlcasts it to astr.I opened a CPython issue to fix this upstream (as PyPy use CPython stdlib), but I still believe rich should not fail here, as you generally do not want your error path to error itself.
alexprengere commentedon Sep 26, 2026
I pushed a fix in my fork here, but I cannot create a PR as I think those are limited to collaborators.
EDfwy1 commentedon Sep 27, 2026
Hi! I ran into this bug and dug into it. Root cause found, along with a minimal fix and a regression test — ready on my fork if a PR would be welcome.
Root cause
In
rich/traceback.py, theTraceback._render_stack()handler builds a_SyntaxErrorfrom anySyntaxErrorsubclass:But
SyntaxError.msgis not guaranteed to be astr.xml.etree.ElementTree.ParseError(andxml.parsers.expat.ExpatError) carry exception-specificmsgvalues — when aParseErroris constructed from anExpatErrorinstance,msgis theExpatErrorobject itself, not a string. Rich'shighlighter()requiresstr, so rendering crashes with:Minimal repro
Fix (one line)
Coerce the message to
strinrich/traceback.py:Regression test
Added
tests/test_traceback.py::test_syntax_error_non_str_message(raises aParseErrorwrapping anExpatError, prints the exception, asserts"ParseError"appears in the output). Passes locally; the traceback test suite is green with the change.Ready-to-merge branch
Since PR creation on this repo is currently limited to collaborators, the complete fix (fix + regression test + CHANGELOG entry) is on my fork:
Branch:
EDfwy1:fix/traceback-non-str-syntax-message(commit90a4936)If a maintainer could either open the PR from that branch, or grant me PR access, I'd be happy to submit it properly. Thanks!