#197110·pytorch

[dynamo] `str(KeyError(...))` inside a traced region omits the quotes CPython adds (`"'k'"` in eager, `'k'` compiled)

Author: cuiliaomei-beepCreated Sep 15, 2026Updated Sep 17, 2026
Labelstriagedmodule: correctness (silent)oncall: pt2module: dynamobot-triaged

Describe the bug

Summary. str() of a caught KeyError inside a fully traced compiled function does not match eager: CPython's KeyError.__str__ returns the repr of the key ("'k'"), while Dynamo returns the plain message ('k'), including in f-strings and for a KeyError raised by a dict lookup; repr(e), e.args, other exception types, and code after a graph break behave like eager (torch 2.14.0 and nightly). A user who matches on str(e) or logs it sees different text once the function is compiled. Because the exception variable dispatches __str__ to the base implementation rather than to KeyError.__str__, the outcome is decided by whether the try block was traced or resumed.

CPython's KeyError.__str__ returns the repr of a single argument (str(KeyError("k")) == "'k'"). Inside a torch.compiled function that Dynamo traces fully, str(e) for a caught KeyError returns the plain message instead:

import torch


def f(x):
    try:
        raise KeyError("k")
    except KeyError as e:
        return x, str(e), f"key error: {e}"


def g(x):
    d = {}
    try:
        d["missing"]
    except KeyError as e:
        return x, str(e)


x = torch.zeros(1)
print(f(x)[1:])                                             # ("'k'", "key error: 'k'")
print(torch.compile(f, backend="eager", fullgraph=True)(x)[1:])   # ('k', 'key error: k')
print(g(x)[1], torch.compile(g, backend="eager", fullgraph=True)(x)[1])   # 'missing' 'missing' -> "'missing'" vs 'missing'

Output (torch 2.14.0+cpu and 2.15.0.dev20260911+cpu):

("'k'", "key error: 'k'")
('k', 'key error: k')
'missing' missing

repr(e) and e.args are not affected. Other exception types (ValueError("v")'v') agree because their __str__ is the base one. If the try block sits after a graph break (e.g. torch._dynamo.graph_break() right before it), the real exception object is used and the result matches eager — which is how the differential test first noticed it: the same program gave the right string in the resumed part and the wrong one in the traced part.

Expected: str(e) identical to eager (Dynamo's exception variable should dispatch __str__ to the concrete exception class, KeyError.__str__, not BaseException.__str__).

Error logs

No error; string differs.

Versions

(torch 2.14.0+cpu, Windows 11, Python 3.14; same on the 2026-09-11 nightly wheel, and on Linux with Python 3.11: torch 2.14.0+cpu and 2.15.0.dev20260912+cpu.)

cc @chauhang @penguinwu @voznesenskym @EikanWang @jgong5 @Guobing-Chen @XiaobingSuper @zhuhaozhe @blzheng @wenzhe-nrv @jiayisunx @kadeng @amjames @jataylo @azahed98