LangGraph Subgraphs, Send API & LangGraph Platform Deploy Errors — Fix Guide (2026)
Deployment · Subgraphs, Send & Platform Severity: High

LangGraph Subgraphs, Send API & LangGraph Platform Deploy Errors

Once your graph is more than a linear pipeline, three things start to bite: subgraphs that don't see the parent's state, Send fan-out that clobbers reducers, and LangGraph Platform deploys that fail on a missing langgraph.json. Here's the definitive guide to the three.

TL;DRSubgraphs share only the state keys that exist in both parent and child schemas — the parent has to declare a key the subgraph writes, or the write is dropped. Send(node, state) fans out to multiple invocations of one node; the receiving state must have reducers for fields that will merge across branches. LangGraph Platform requires a langgraph.json at the repo root plus env-var declarations; missing either causes silent deploy failures.

Real error messages you'll see

Subgraph state key dropped
Subgraph state key dropped
# Parent state has {messages, question} — subgraph writes {messages, docs}.
# After subgraph runs, parent state has messages (merged) but no docs.
# Root cause: parent schema has no `docs` field, so LangGraph drops it silently.
Send fan-out overwrites reducer field
Send fan-out overwrites reducer field
InvalidUpdateError: Concurrent updates to field 'messages' without a reducer.
  at Pregel.astream() — 5 Send() targets all wrote to `messages` in parallel.
# Fix: annotate messages with add_messages reducer; without it, parallel writes conflict.
LangGraph Platform deploy failure
LangGraph Platform deploy failure
Error: Missing langgraph.json in repository root. LangGraph Platform requires a langgraph.json manifest declaring graphs, dependencies, and env vars.
# Add langgraph.json at repo root: {"dependencies": ["."], "graphs": {"agent": "./src/graph.py:app"}, "env": ".env"}.

Subgraph state-sharing rules

CaseBehavior
Parent and subgraph share a key with same reducerReducer merges updates from both
Parent has key, subgraph doesn'tSubgraph can't read or write; key is invisible to it
Subgraph has key, parent doesn'tSubgraph's writes are dropped when returning to parent
Same key, different typesRuntime error at first update
Same key, different reducersParent's reducer wins in the outer graph

langgraph.json fields (Platform / Cloud)

FieldTypePurpose
dependencieslist[str]Local packages to install (usually ["."])
graphsdict[str, str]Map graph name → ./path/to/module.py:variable
envstrPath to .env file (or list of KEY=VALUE lines)
python_versionstre.g. "3.12" — pin to match your local env
dockerfile_lineslist[str]Extra Dockerfile RUN lines for system deps
pip_config_filestrOptional pip config (private index, etc.)

Root causes (ranked by frequency)

Based on LangGraph developer reports; percentages sum to 100%.

  • 23%
    Subgraph writes a state key the parent doesn't declare. Writes are silently dropped when control returns to the parent. Add the key to the parent schema.
  • 18%
    Send() fan-out to a node without reducers. Parallel branches all try to write the same field; without a reducer LangGraph raises InvalidUpdateError.
  • 15%
    Missing langgraph.json at repo root. LangGraph Platform can't discover the graph. Add the manifest and re-deploy.
  • 12%
    Environment variables not declared in langgraph.json. API keys land as None at runtime; downstream calls fail with 401.
  • 11%
    Wrong graph path in langgraph.json. Path points at a factory function that returns a graph, but Platform expects a compiled app variable. Point to the compiled variable directly.
  • 9%
    Subgraph checkpoint namespace conflict. Two subgraphs share the same checkpoint_ns; their state overwrites in the checkpointer. Namespace them explicitly.
  • 7%
    Send payload not JSON-serializable. Fan-out payload contains a non-serializable object (open file, DB cursor). Persisting or passing across the boundary fails.
  • 5%
    Python version drift between local and Platform. Local dev on 3.13, Platform builds on 3.12 default. Type hints or stdlib features silently break at runtime.

How to fix it

Fix #1

Declare shared state keys in both parent and subgraph schemas

Fixes silently-dropped subgraph writes.

LangGraph subgraphs are executed with a projected state — only keys that exist in the subgraph schema are visible inside. When the subgraph returns, only keys that also exist in the parent are merged back. The rule is straightforward once you know it: every state key that flows through a subgraph must appear in both schemas, with compatible types and reducers.

subgraph_state_sharing.pypython
from typing import Annotated, TypedDict
from operator import add
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages


class ChildState(TypedDict):
    messages: Annotated[list, add_messages]
    docs: Annotated[list, add]
    citations: Annotated[list, add]


def retrieve(state: ChildState) -> dict:
    return {"docs": ["doc1", "doc2"]}


def cite(state: ChildState) -> dict:
    return {"citations": [f"[{i}]" for i in range(len(state["docs"]))]}


child_graph = StateGraph(ChildState)
child_graph.add_node("retrieve", retrieve)
child_graph.add_node("cite", cite)
child_graph.add_edge(START, "retrieve")
child_graph.add_edge("retrieve", "cite")
child_graph.add_edge("cite", END)
child_app = child_graph.compile()


# PARENT declares docs and citations to receive them from child
class ParentState(TypedDict):
    messages: Annotated[list, add_messages]
    question: str
    docs: Annotated[list, add]
    citations: Annotated[list, add]


parent_graph = StateGraph(ParentState)
parent_graph.add_node("child", child_app)
parent_graph.add_edge(START, "child")
parent_graph.add_edge("child", END)
parent_app = parent_graph.compile()
Note: Keys that are internal to the subgraph (scratch, intermediate) should live only in the child schema — the parent won't see them, which is the intended encapsulation.
Fix #2

Use Send() with reducers on every merged field

Fixes InvalidUpdateError on parallel Send fan-out.

Send(node, state) creates one execution per Send. Fan out to 5 Sends and that node runs 5 times in parallel. All results merge back into the graph state, so every field the node writes must have a reducer to combine parallel updates.

send_fanout.pypython
from typing import Annotated, TypedDict
from operator import add
from langgraph.types import Send
from langgraph.graph import StateGraph, START, END


class State(TypedDict):
    topics: list[str]
    summaries: Annotated[list, add]         # reducer required for parallel Send merge
    errors: Annotated[list, add]


def fanout(state):
    return [Send("summarize_one", {"topic": t}) for t in state["topics"]]


def summarize_one(state) -> dict:
    try:
        summary = llm.invoke(f"Summarize {state['topic']} in 20 words.").content
        return {"summaries": [summary]}
    except Exception as e:
        return {"errors": [f"{state['topic']}: {e}"]}


def combine(state):
    return {"messages": [("assistant", "\n".join(state["summaries"]))]}


graph = StateGraph(State)
graph.add_node("summarize_one", summarize_one)
graph.add_node("combine", combine)
graph.add_conditional_edges(START, fanout, ["summarize_one"])
graph.add_edge("summarize_one", "combine")
graph.add_edge("combine", END)
app = graph.compile()

# Cap fan-out via config
result = app.invoke(
    {"topics": ["a", "b", "c"], "summaries": [], "errors": []},
    {"max_concurrency": 10},
)
Note: Set max_concurrency in the runnable config to throttle parallel Send executions — critical when the fan-out target makes rate-limited API calls.
Fix #3

Write a complete langgraph.json for LangGraph Platform / Cloud

Fixes silent deploy failures and missing env vars.

LangGraph Platform (formerly LangGraph Cloud) reads langgraph.json at the repo root to discover graphs, install dependencies, and inject env vars. A missing or incomplete file causes builds to fail with unclear errors. Point graphs at compiled app variables (not factory functions), declare every env var, and pin the Python version.

langgraph.jsonjson
{
  "dependencies": ["."],
  "graphs": {
    "chat_agent": "./src/agents/chat.py:app",
    "research_agent": "./src/agents/research.py:app"
  },
  "env": ".env",
  "python_version": "3.12",
  "dockerfile_lines": [
    "RUN apt-get update && apt-get install -y --no-install-recommends libmagic1 && rm -rf /var/lib/apt/lists/*"
  ]
}
src/agents/chat.pypython
# ✅ Export a COMPILED app variable at module level
# LangGraph Platform imports this variable directly

import os
from langgraph.graph import StateGraph, START, END, MessagesState
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from psycopg_pool import AsyncConnectionPool


def _build_graph(checkpointer):
    g = StateGraph(MessagesState)
    g.add_node("responder", responder)
    g.add_edge(START, "responder")
    g.add_edge("responder", END)
    return g.compile(checkpointer=checkpointer)


# Platform provides POSTGRES_URI at build/run time via the langgraph.json env
_pool = AsyncConnectionPool(
    os.environ["POSTGRES_URI"],
    min_size=2, max_size=20,
    kwargs={"prepare_threshold": None, "autocommit": True},
    open=False,  # opened lazily
)
_checkpointer = AsyncPostgresSaver(_pool)

# THE variable Platform expects — top-level, compiled
app = _build_graph(_checkpointer)


# ❌ ANTI-PATTERN — Platform can't call a factory
# def build_app():
#     return _build_graph(_checkpointer)
# Platform will fail with: "chat.py:app is not a compiled graph"
.envbash
# ✅ Declare every env var; Platform injects these at runtime
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
POSTGRES_URI=postgresql://user:pass@host:5432/langgraph
LANGSMITH_API_KEY=lsv2_...
LANGSMITH_PROJECT=my-agent-prod
LOG_LEVEL=INFO
Note: The dockerfile_lines field is where system deps go — libmagic, tesseract, headless-chrome bits. Anything apt-get install-able. Keep it minimal to keep build times fast.

Prevention checklist

  • Every state key that flows through a subgraph must appear in both the parent and the child schema with compatible types and reducers.
  • When using Send, every field the receiving node writes must have a reducer — parallel writes need explicit merge semantics.
  • Cap Send fan-out (max_concurrency in config, or truncate the input list) to avoid runaway parallelism.
  • For LangGraph Platform deploys, always add langgraph.json at repo root with dependencies, graphs, and env declared.
  • Point graphs entries at a compiled app variable (module.py:app), not a factory function.
  • Pin python_version in langgraph.json to match local dev — silent version drift breaks type hints and stdlib features.
  • Declare every env var your graph reads in langgraph.json; missing keys arrive as None and cause downstream 401s.

Frequently asked questions

When should I use a subgraph vs an inline set of nodes?

Subgraph when the logic is reusable across multiple parent graphs, or when it has its own internal state that shouldn't leak to the parent. Inline nodes when the flow is single-use and short. Subgraphs cost a bit of ceremony (schema alignment, namespaced checkpoints) but pay back when you need to test the subgraph in isolation or reuse it.

How is Send different from a conditional edge that returns a list?

Send(node, state) lets each parallel branch receive a different state — you can tailor the input per branch. A conditional edge returning list[str] just names the downstream nodes; they all inherit the source state as-is. Use Send when the fan-out inputs differ (e.g. one topic per branch); use list-returning conditional edges when the fan-out is symmetric.

What's LangGraph Platform vs LangGraph Cloud vs self-hosting?

LangGraph Platform is the managed service (formerly branded LangGraph Cloud). It runs your compiled graphs with Postgres checkpointing, streaming APIs, and observability integrated with LangSmith. Self-hosting means running your own FastAPI/etc. wrapping the compiled graph. Platform is faster to ship; self-hosting is cheaper at scale and gives you full runtime control.

Can I mix subgraphs with different state schemas?

Yes. Each subgraph has its own schema. What matters is the projection at the boundary: parent state → subgraph input (only shared keys visible), subgraph output → parent state (only shared keys merged). Non-shared keys are invisible / dropped. Design the schemas around what needs to cross the boundary and keep internal state internal.

Why does my Send fan-out ignore state changes from other branches?

Because parallel Sends see the state as it was at the moment fan-out started. If Send 3 reads state["docs"], it sees the docs available before the fan-out — not any updates Sends 1 and 2 made during their runs. Reducers merge all branches' outputs after they complete, not during. If branches need to see each other's work, they must run sequentially, not in parallel.

Related errors