## TL;DR
LangGraph requires every node to write to at least one state channel; a node that returns `None`, an empty dict with no matching keys, or only side effects triggers this error. Fix it by returning a dict that updates at least one declared state key (or a Command), and make sure conditional branches never fall through without returning.

```text
"must write to at least one of []" langgraph
```

## Use this when
- A LangGraph node raises this error at runtime
- An agent generated the graph and a node body only prints or logs
- Conditional logic has a branch that returns nothing

## Not for this skill when
- The graph loops forever (thats GraphRecursionError)
- State updates fail validation (thats an invalid-update error)
- Edges route to the wrong node (thats a routing bug)

## Steps

1. Find the node the traceback names and read its return paths:

```python
def my_node(state):
    result = do_work(state)
    # every path through here must return a state update
    return {"messages": result}
```
Expected output: the node returns a dict keyed by a real state channel. If any branch returns None or a bare value, thats the bug.

2. Fix branches that fall through. The classic shape is an if/else where one side forgets to return:

```python
def my_node(state):
    if state["needs_lookup"]:
        return {"docs": lookup(state["query"])}
    return {"docs": []}  # dont fall off the end
```
Expected output: all paths return a dict. An empty list is a valid update; returning nothing is not.

3. If the node intentionally changes control flow, return a Command instead:

```python
from langgraph.types import Command
def router(state):
    return Command(update={"route": "b"}, goto="node_b")
```
Expected output: the graph accepts the Command as a valid write (it updates the `route` channel) and follows the goto.

4. Check that the returned keys exist in the state schema. Writing to a key that isnt declared is also rejected:

```python
class State(TypedDict):
    messages: list
    docs: list
```
Expected output: the node's return keys are a subset of the schema. A typo like `{"doc": ...}` vs `{"docs": ...}` fails the same check.

## Variant phrasings

### node returns a string instead of a dict
Wrap it: `return {"messages": [result]}`. Bare values are never valid node outputs.

### error only on some inputs
One input takes the branch that returns nothing. Add a default return at the end of every node as a safety net.

## Why it happens
LangGraph models state as named channels, and a node run is only meaningful if it writes to at least one. The empty list in the message means the node wrote to zero channels, which the runtime treats as a programming error rather than a no-op. Agent-generated graphs hit this a lot because generated node bodies often end with a print statement and no return.

## Edge cases
- Async nodes have the same rule; an `async def` node without a return fails identically.
- Subgraphs must also produce writes; a subgraph that only reads state trips the check at the parent level.
- Returning `{"messages": []}` is fine (empty update to a real channel); returning `{}` is not.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_llljMSLy3LLUePA8XGtf4g
