How to Fix 'agent infinite loop during development' in LangGraph (TypeScript)
When LangGraph throws an agent infinite loop during development error, it usually means your graph keeps routing back into the agent node without ever reaching a stop condition. In practice, this happens when the model keeps producing tool calls, your conditional edge never exits, or your state never changes in a way that breaks the cycle.
This is common in TypeScript projects where the graph compiles fine, runs once, then gets stuck during iterative development because a node returns the wrong shape or your routing logic is too permissive.
The Most Common Cause
The #1 cause is a bad conditional edge that always routes back to the agent, even after the assistant has already produced a final answer.
In LangGraph terms, you usually see the loop around nodes like agent, tools, and __end__. The graph keeps hitting something like:
- •
agent -> tools - •
tools -> agent - •
agent -> tools
until LangGraph detects repeated execution and throws an infinite loop error.
Here’s the broken pattern:
| Broken | Fixed |
|---|---|
Always routes back to agent | Routes to __end__ when there are no tool calls |
import { StateGraph, START, END } from "@langchain/langgraph";
import { AIMessage } from "@langchain/core/messages";
type State = {
messages: any[];
};
const shouldContinue = (state: State) => {
const last = state.messages[state.messages.length - 1];
// Broken: if you always return "tools" or "agent", you never terminate
return "tools";
};
const graph = new StateGraph<State>({
channels: {
messages: {
value: (x: any[], y: any[]) => x.concat(y),
default: () => [],
},
},
})
.addNode("agent", async (state) => {
return {
messages: [new AIMessage("Final answer here")],
};
})
.addNode("tools", async () => ({ messages: [] }))
.addEdge(START, "agent")
.addConditionalEdges("agent", shouldContinue, {
tools: "tools",
agent: "agent",
end: END,
})
.addEdge("tools", "agent");
The fixed version checks whether the last assistant message actually contains tool calls. If not, it ends the graph.
import { StateGraph, START, END } from "@langchain/langgraph";
import { AIMessage } from "@langchain/core/messages";
type State = {
messages: any[];
};
const shouldContinue = (state: State) => {
const last = state.messages[state.messages.length - 1];
if (last?.tool_calls?.length) return "tools";
return END;
};
const graph = new StateGraph<State>({
channels: {
messages: {
value: (x: any[], y: any[]) => x.concat(y),
default: () => [],
},
},
})
.addNode("agent", async (state) => ({
messages: [new AIMessage("Final answer here")],
}))
.addNode("tools", async () => ({ messages: [] }))
.addEdge(START, "agent")
.addConditionalEdges("agent", shouldContinue, {
tools: "tools",
[END]: END,
})
.addEdge("tools", "agent");
The key point is simple: only route to tools when there are actual tool calls. If your assistant already answered, stop.
Other Possible Causes
1. Your tool node does not append a tool result
If your tool node returns nothing useful, the model sees no progress and asks for the same tool again.
// Broken
.addNode("tools", async () => {
return { messages: [] };
});
// Fixed
.addNode("tools", async (state) => {
return {
messages: [
{
type: "tool",
content: "Fetched account balance",
tool_call_id: state.messages.at(-1)?.tool_calls?.[0]?.id,
},
],
};
});
2. Your state reducer overwrites instead of appending
If you replace messages on every step, LangGraph loses context and may re-enter the same branch.
// Broken
messages: {
value: (_prev, next) => next,
}
// Fixed
messages: {
value: (prev, next) => prev.concat(next),
}
3. The model is configured to always call tools
If you force tool calling on every response, the agent never produces a final answer.
// Broken
const model = llm.bindTools(tools, { tool_choice: "any" });
// Better
const model = llm.bindTools(tools);
If you need strict control, only force tools in specific nodes or prompts. Do not leave it on globally unless you really want every turn to be a tool turn.
4. Your recursion limit is too high while debugging
This does not cause the loop by itself, but it hides the real problem until later. In development, keep it low so failures show up quickly.
const result = await graph.invoke(
{ messages },
{ recursionLimit: 10 }
);
LangGraph will eventually throw an error like:
- •
Error: Recursion limit of X reached without hitting a stop condition - •
GraphRecursionError - •or a similar runtime guard depending on version
How to Debug It
- •
Log every node transition
- •Print node name and last message type.
- •You want to see whether execution is bouncing between
agentandtools.
- •
Inspect the last assistant message
- •Check whether it contains
tool_calls. - •If it does not, your conditional edge should go to
END, not back into the loop.
- •Check whether it contains
- •
Verify your reducer behavior
- •Confirm that
messagesgrows over time. - •If each step resets state, your agent will repeat itself.
- •Confirm that
- •
Temporarily disable tools
- •Run the agent with tools removed.
- •If the loop disappears, the issue is in tool routing or tool output formatting.
A practical debug helper looks like this:
const debugNode = (name: string) => async (state: any) => {
const last = state.messages?.at(-1);
console.log(`[${name}] last=`, JSON.stringify(last, null, 2));
};
Use that on both agent and tools. If you see repeated identical payloads, you’ve found your cycle.
Prevention
- •Keep your conditional routing strict:
- •route to tools only when
last.tool_calls?.length > 0
- •route to tools only when
- •Make message reducers append-only unless you have a very specific reason to overwrite state.
- •Add a low
recursionLimitin development so loops fail fast instead of burning time and tokens. - •Test one complete happy path before adding more tools or branches.
If you want one rule to remember: LangGraph agents need an explicit exit path. No exit path means no finish line; just another turn through the loop.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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