python/cpython · error · ValueError

Node can't use cause without an exception.

Error message

Node can't use cause without an exception.

What it means

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.

Source

Thrown at Lib/_ast_unparse.py:341

            self.write("yield")
            if node.value:
                self.write(" ")
                self.set_precedence(_Precedence.ATOM, node.value)
                self.traverse(node.value)

    def visit_YieldFrom(self, node):
        with self.require_parens(_Precedence.YIELD, node):
            self.write("yield from ")
            if not node.value:
                raise ValueError("Node can't be used without a value attribute.")
            self.set_precedence(_Precedence.ATOM, node.value)
            self.traverse(node.value)

    def visit_Raise(self, node):
        self.fill("raise")
        if not node.exc:
            if node.cause:
                raise ValueError(f"Node can't use cause without an exception.")
            return
        self.write(" ")
        self.traverse(node.exc)
        if node.cause:
            self.write(" from ")
            self.traverse(node.cause)

    def do_visit_try(self, node):
        self.fill("try", allow_semicolon=False)
        with self.block():
            self.traverse(node.body)
        for ex in node.handlers:
            self.traverse(ex)
        if node.orelse:
            self.fill("else", allow_semicolon=False)
            with self.block():
                self.traverse(node.orelse)
        if node.finalbody:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Set node.exc to an ast expression (e.g. ast.Name(id='ValueError', ctx=ast.Load())) before unparsing, or construct a real raise statement.
  2. If you intended a bare 'raise', set node.cause = None so the node reverts to a legal form.
  3. Add an assertion/guard in your transformer that a Raise node never keeps a cause when its exc is removed.
  4. Build the node with ast.Raise(exc=..., cause=...) in one step instead of mutating fields afterwards.

Example fix

// before
node = ast.Raise(exc=None, cause=ast.Name(id='KeyError', ctx=ast.Load()))
ast.unparse(node)  # ValueError: Node can't use cause without an exception.

// after
node = ast.Raise(
    exc=ast.Call(func=ast.Name(id='ValueError', ctx=ast.Load()), args=[], keywords=[]),
    cause=ast.Name(id='KeyError', ctx=ast.Load()),
)
ast.unparse(node)  # 'ValueError() from KeyError'
Defensive patterns

Strategy: validation

Validate before calling

import ast

def valid_raise(node: ast.Raise) -> bool:
    return not (node.exc is None and node.cause is not None)

# before unparsing any transformed tree:
for n in ast.walk(tree):
    if isinstance(n, ast.Raise) and not valid_raise(n):
        n.cause = None  # or set n.exc

Type guard

def is_valid_raise(node: ast.AST) -> bool is not applicable; use: 
def raise_ok(n: ast.AST) -> bool:
    return not (isinstance(n, ast.Raise) and n.exc is None and n.cause is not None)

Try / catch

try:
    src = ast.unparse(tree)
except ValueError as e:
    if 'cause without an exception' in str(e):
        for n in ast.walk(tree):
            if isinstance(n, ast.Raise) and n.exc is None:
                n.cause = None
        src = ast.unparse(tree)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/242418f5e6170959. Report an issue: GitHub.