{"record":{"id":"242418f5e6170959","repo":"python/cpython","slug":"node-can-t-use-cause-without-an-exception","errorCode":null,"errorMessage":"Node can't use cause without an exception.","messagePattern":"Node can't use cause without an exception\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_ast_unparse.py","lineNumber":341,"sourceCode":"            self.write(\"yield\")\n            if node.value:\n                self.write(\" \")\n                self.set_precedence(_Precedence.ATOM, node.value)\n                self.traverse(node.value)\n\n    def visit_YieldFrom(self, node):\n        with self.require_parens(_Precedence.YIELD, node):\n            self.write(\"yield from \")\n            if not node.value:\n                raise ValueError(\"Node can't be used without a value attribute.\")\n            self.set_precedence(_Precedence.ATOM, node.value)\n            self.traverse(node.value)\n\n    def visit_Raise(self, node):\n        self.fill(\"raise\")\n        if not node.exc:\n            if node.cause:\n                raise ValueError(f\"Node can't use cause without an exception.\")\n            return\n        self.write(\" \")\n        self.traverse(node.exc)\n        if node.cause:\n            self.write(\" from \")\n            self.traverse(node.cause)\n\n    def do_visit_try(self, node):\n        self.fill(\"try\", allow_semicolon=False)\n        with self.block():\n            self.traverse(node.body)\n        for ex in node.handlers:\n            self.traverse(ex)\n        if node.orelse:\n            self.fill(\"else\", allow_semicolon=False)\n            with self.block():\n                self.traverse(node.orelse)\n        if node.finalbody:","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_ast_unparse.py#L323-L359","documentation":"Raised by ast.unparse() (via Lib/_ast_unparse.py visit_Raise) when a Raise AST node has exc=None but a non-None cause. In valid Python source this cannot happen because 'raise from cause' always requires an expression, so the node was almost certainly built programmatically or mangled by a transformation pass. The unparser refuses to emit a bare 'from' clause with nothing to raise.","triggerScenarios":"Calling ast.unparse(node) on an ast.Raise where node.exc is None and node.cause is not None. Typical producers: AST rewrite/optimization passes that replace the exception expression with None, hand-built trees that set cause while leaving exc unset, or copying a cause onto a re-raise node.","commonSituations":"Writing a lint/transform tool that edits Raise nodes; porting code that constructs AST nodes literally; partial construction of a Raise node where exc is filled in later but unparse is called too early.","solutions":["Set node.exc to an ast expression (e.g. ast.Name(id='ValueError', ctx=ast.Load())) before unparsing, or construct a real raise statement.","If you intended a bare 'raise', set node.cause = None so the node reverts to a legal form.","Add an assertion/guard in your transformer that a Raise node never keeps a cause when its exc is removed.","Build the node with ast.Raise(exc=..., cause=...) in one step instead of mutating fields afterwards."],"exampleFix":"// before\nnode = ast.Raise(exc=None, cause=ast.Name(id='KeyError', ctx=ast.Load()))\nast.unparse(node)  # ValueError: Node can't use cause without an exception.\n\n// after\nnode = ast.Raise(\n    exc=ast.Call(func=ast.Name(id='ValueError', ctx=ast.Load()), args=[], keywords=[]),\n    cause=ast.Name(id='KeyError', ctx=ast.Load()),\n)\nast.unparse(node)  # 'ValueError() from KeyError'","handlingStrategy":"validation","validationCode":"import ast\n\ndef valid_raise(node: ast.Raise) -> bool:\n    return not (node.exc is None and node.cause is not None)\n\n# before unparsing any transformed tree:\nfor n in ast.walk(tree):\n    if isinstance(n, ast.Raise) and not valid_raise(n):\n        n.cause = None  # or set n.exc","typeGuard":"def is_valid_raise(node: ast.AST) -> bool is not applicable; use: \ndef raise_ok(n: ast.AST) -> bool:\n    return not (isinstance(n, ast.Raise) and n.exc is None and n.cause is not None)","tryCatchPattern":"try:\n    src = ast.unparse(tree)\nexcept ValueError as e:\n    if 'cause without an exception' in str(e):\n        for n in ast.walk(tree):\n            if isinstance(n, ast.Raise) and n.exc is None:\n                n.cause = None\n        src = ast.unparse(tree)\n    else:\n        raise","preventionTips":["In AST transform passes, treat exc and cause as a pair: clear cause whenever exc is removed.","Unit-test unparsing of every tree your transformer emits.","Prefer building Raise nodes in one constructor call instead of mutating fields."],"tags":["ast","codegen","ast-unparse","developer-error"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}