Skip to content

Fix two crashes from unguarded calls into the optional Rich dependency - #837

Open
pranjalm37 wants to merge 2 commits into
hynek:mainfrom
pranjalm37:fix/rich-defensive-handling-655-576
Open

Fix two crashes from unguarded calls into the optional Rich dependency#837
pranjalm37 wants to merge 2 commits into
hynek:mainfrom
pranjalm37:fix/rich-defensive-handling-655-576

Conversation

@pranjalm37

Copy link
Copy Markdown

Summary

Fixes #655 and #576. Both are crashes caused by calling into the optional
rich dependency without the same defensive handling structlog applies
elsewhere.

#655to_repr() propagates exceptions from Rich's introspection

rich.pretty.traverse(...).render() was called with no error handling,
unlike the non-Rich fallback path right below it, which explicitly catches
everything ("Also catch all errors, similarly to safe_str()"). If Rich's
own object introspection raises (e.g. on an object with unusual attribute
access, as in the anyio-internal object from the report), the exception
propagates out of to_repr() and breaks exception logging entirely — at
exactly the moment you need it most.

Fix: wrap the Rich call in try/except and fall back to the manual repr
algorithm on failure, so to_repr() actually honors its own docstring
("Get repr string for an object, but catch errors").

#576RichTracebackFormatter crashes on older Rich versions

RichTracebackFormatter.__call__() always passes locals_hide_dunder and
locals_hide_sunder to Traceback.from_exception(). Those arguments were
only added in Rich 13.1.0, so on older Rich (e.g. 13.0.1, as in the report)
this raises TypeError: Traceback.from_exception() got an unexpected keyword argument 'locals_hide_dunder', crashing the console renderer
outright.

Fix: inspect Traceback.from_exception's signature once (module-level,
next to the existing Rich import) and only pass the kwargs the installed
Rich version actually supports — mirroring the hasattr(tb, "code_width")
compatibility check already a few lines below, which exists for the same
reason (forward compat with code_width in that case, backward compat
with these two here).

Test plan

  • test_to_repr_rich_error — monkeypatches rich.pretty.traverse to
    raise and asserts to_repr() still returns a correct plain repr
    instead of propagating.
  • test_old_rich_missing_locals_hide_params — monkeypatches the
    supported-params set to simulate an old Rich install and asserts
    locals_hide_dunder/locals_hide_sunder are omitted from the call.
  • Manually reproduced Version compatibility check for optional dependencies #576 exactly by installing rich==13.0.1 locally
    and confirmed the TypeError before the fix / clean success after.
  • Full suite: pytest tests/ → 902 passed, 21 skipped (up from 900
    passed pre-change — the 2 new tests).
  • ruff check / ruff format clean on all changed files.
  • Added a changelog entry under ## [Unreleased].

…ndency

- tracebacks.to_repr(): rich.pretty.traverse() was called with no error
  handling, unlike the non-Rich fallback right below it which explicitly
  catches everything. Any exception raised during Rich's own object
  introspection (e.g. from unusual attribute access) would propagate and
  break exception logging entirely, at exactly the moment it's needed most.
  Now falls back to the manual repr algorithm on failure. (hynek#655)

- dev.RichTracebackFormatter: always passed locals_hide_dunder and
  locals_hide_sunder to Traceback.from_exception(), but those arguments
  were only added in Rich 13.1.0. On older Rich versions this raised
  TypeError: unexpected keyword argument, crashing the console renderer.
  Now inspects Traceback.from_exception()'s signature once and only passes
  the kwargs the installed Rich version actually supports, mirroring the
  existing hasattr(tb, "code_width") compatibility pattern already used
  a few lines below for the same reason. (hynek#576)

Added regression tests for both and a changelog entry.
@codspeed-hq

codspeed-hq Bot commented Aug 8, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 10 untouched benchmarks


Comparing pranjalm37:fix/rich-defensive-handling-655-576 (e9d9f21) with main (bf3cfd0)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (ab24a26) during the generation of this report, so bf3cfd0 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Without an explicit annotation, mypy inferred a narrow union type from the
dict literal, which it then rejected when unpacked into
Traceback.from_exception()'s specifically-typed parameters. CI caught this.
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.

to_repr and rich breaks logging

1 participant