Unit 04.01: The branch everyone forgets: failure
Every node can fail. In most graphs exactly zero of them say what happens when they do, which means the answer is: the run dies and the state is lost.
Catch into state, route on the field
A node that catches its own exceptions and writes them into an error field turns a crash into a branch. The router then handles it like any other condition.
The code runs one node against three payloads, two of which break it.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
payload: dict
error: str
result: str
def fetch(state: State):
"""Nodes should catch their own exceptions and put them in state."""
try:
value = state["payload"]["amount"] * 2
return {"result": f"ok: {value}", "error": ""}
except (KeyError, TypeError) as exc:
return {"result": "", "error": f"{type(exc).__name__}: {exc}"}
def after(state: State):
return "recover" if state["error"] else END
g = StateGraph(State)
g.add_node("fetch", fetch)
g.add_node("recover", lambda s: {"result": "used cached value"})
g.add_edge(START, "fetch")
g.add_conditional_edges("fetch", after, {"recover": "recover", END: END})
g.add_edge("recover", END)
app = g.compile()
for payload in ({"amount": 10}, {}, {"amount": None}):
out = app.invoke({"payload": payload, "error": "", "result": ""})
print(f"{str(payload):20} -> {out['result']!r:24} error={out['error']!r}")
# An uncaught exception inside a node kills the whole run, discards the state,
# and gives the user a stack trace. Catching it into `error` turns a crash into
# a branch you designed.
All three inputs produce a completed run. The two failures land in recover, which uses a cached value; without the catch, the second and third would have raised and taken the whole run with them - including the work already done by earlier nodes.
That last point is the real cost of an uncaught exception in a graph. It is not just this node failing; it is every completed step upstream being discarded, along with any state a human had already reviewed.
The mistake this prevents
The mistake is catching bare Exception and continuing silently. Catch the exceptions you expect, name them in the error field, and let anything unexpected propagate - an unknown failure that continues quietly produces a wrong result instead of a handled one.
Takeaway
Nodes catch their own expected exceptions into an error field and let the router branch on it. An uncaught exception in one node discards the state of the entire run.
