How to Fix 'state not updating' in LangChain (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
state-not-updatinglangchaintypescript

If you’re seeing state not updating in a LangChain TypeScript app, it usually means your graph or chain is running, but the state object you expect to change is either being replaced incorrectly, mutated in the wrong place, or never returned from the node. This shows up a lot in LangGraph-style workflows, agent loops, and any setup where you pass structured state between steps.

The error often appears as “my node runs, but downstream steps still see the old value” or “the reducer never fires.” In practice, this is almost always a state shape problem, not an LLM problem.

The Most Common Cause

The #1 cause is mutating state directly instead of returning a new partial update in the format LangChain/LangGraph expects.

In TypeScript, this usually happens when developers treat state like a normal mutable object inside a node function. That works in plain JS code, but LangGraph nodes are expected to return updates, not silently mutate input.

Broken patternCorrect pattern
Mutates state.messages.push(...) and returns nothing usefulReturns { messages: [...] } or a partial update object
Assumes mutation will be trackedUses immutable updates and explicit return values

Broken code

import { StateGraph } from "@langchain/langgraph";
import { HumanMessage, AIMessage } from "@langchain/core/messages";

type AgentState = {
  messages: Array<HumanMessage | AIMessage>;
};

const badNode = async (state: AgentState) => {
  state.messages.push(new AIMessage("Updated response")); // mutation
  // no explicit state update returned
  return {};
};

Fixed code

import { StateGraph } from "@langchain/langgraph";
import { HumanMessage, AIMessage } from "@langchain/core/messages";

type AgentState = {
  messages: Array<HumanMessage | AIMessage>;
};

const goodNode = async (state: AgentState) => {
  return {
    messages: [...state.messages, new AIMessage("Updated response")],
  };
};

If you’re using StateGraph, this matters even more when your schema defines reducers. The graph only merges what you return.

Other Possible Causes

1. Your state schema is missing a reducer

If multiple nodes write to the same key, LangGraph needs to know how to combine values. Without a reducer, later updates can overwrite earlier ones or get ignored depending on how the graph is wired.

import { Annotation } from "@langchain/langgraph";

const StateAnnotation = Annotation.Root({
  messages: Annotation<Array<any>>({
    reducer: (left, right) => left.concat(right),
    default: () => [],
  }),
});

If messages should accumulate over time, define the reducer explicitly.

2. You’re returning the wrong key name

This one is common in TypeScript because the compiler won’t always catch mismatched runtime keys if your node return type is loose.

// Broken
return {
  message: [new AIMessage("hello")], // should be "messages"
};
// Fixed
return {
  messages: [new AIMessage("hello")],
};

A typo like message vs messages looks harmless and produces exactly the “state not updating” symptom downstream.

3. You’re using stale closures inside async code

If you capture state and then update based on an older reference after an await boundary, your node may return stale data.

const node = async (state: AgentState) => {
  const snapshot = state.messages;

  await someAsyncCall();

  return {
    messages: [...snapshot, new AIMessage("late update")],
  };
};

If another step updated the graph state while you were waiting, snapshot is now stale. Prefer reading from the current input only once per node and avoid long-lived references.

4. Your conditional edge never routes back to the updating node

Sometimes the state is fine; your graph just never re-enters the node that performs the update.

graph.addConditionalEdges("check", (state) => {
  if (state.needsRetry) return "retry";
  return "end";
});

If needsRetry never flips because another node failed to update it correctly, or your condition points to the wrong label, you’ll think state isn’t updating when routing is actually broken.

How to Debug It

  1. Log every node input and output
    • Print state at entry and what you return.
    • If input changes but output doesn’t include the expected key, that’s your bug.
const debugNode = async (state: AgentState) => {
  console.log("IN:", JSON.stringify(state));
  const next = {
    messages: [...state.messages, new AIMessage("debug")],
  };
  console.log("OUT:", JSON.stringify(next));
  return next;
};
  1. Check your schema keys

    • Compare returned keys with your Annotation.Root(...) or interface.
    • Watch for singular/plural mismatches like message, messages, chatHistory.
  2. Verify reducers for shared fields

    • If more than one node writes to a field, add a reducer.
    • Without it, updates can be lost or overwritten.
  3. Inspect graph routing

    • Confirm conditional edges point where you think they do.
    • A correct update in a dead branch still looks like “state not updating.”

Prevention

  • Use immutable returns for every LangGraph node.
  • Define explicit reducers for any field that accumulates across nodes.
  • Keep state types strict in TypeScript so wrong keys fail at compile time.
  • Add debug logging around node boundaries before shipping agent workflows.

If you’re building agents for production systems like banking or insurance workflows, treat state as an API contract. Most “state not updating” bugs come from violating that contract with mutation, bad keys, or missing reducers—not from LangChain itself.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides