How to Fix 'agent infinite loop in production' in LangChain (TypeScript)
If you’re seeing agent infinite loop in production, LangChain is telling you the agent kept calling tools or re-entering the executor until it hit its iteration limit. In TypeScript, this usually shows up when the agent never reaches a final answer, keeps selecting the same tool, or your tool output causes the model to ask for another tool call forever.
This is rarely a LangChain bug. It’s usually an agent design issue: bad tool descriptions, missing stop conditions, malformed tool outputs, or a prompt that encourages endless planning.
The Most Common Cause
The #1 cause is a tool loop: the agent calls a tool, gets output that still looks like “work to do,” then calls the same tool again.
Typical runtime error patterns include:
- •
Error: Agent stopped due to max iterations - •
Error: Exceeded max iterations - •
LangChainError: Tool call loop detected - •
OutputParserExceptionfollowed by repeated retries
Here’s the broken pattern and the fixed pattern side by side.
| Broken | Fixed |
|---|---|
| Tool returns ambiguous text that invites another call | Tool returns a final, structured result |
| Agent prompt says “keep using tools until complete” | Prompt tells the model when to stop |
| No iteration cap or no early stopping | Explicit maxIterations and earlyStoppingMethod |
// BROKEN
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const searchTool = {
name: "search_customer",
description: "Search customer data",
schema: z.object({ query: z.string() }),
invoke: async ({ query }: { query: string }) => {
// Bad: vague output makes the agent keep digging
return `Found some results for ${query}. Need more investigation.`;
},
};
const agent = createReactAgent({
llm,
tools: [searchTool],
});
const result = await agent.invoke({
messages: [
{
role: "user",
content: "Find customer John Smith and summarize his status.",
},
],
});
// FIXED
import { ChatOpenAI } from "@langchain/openai";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
const searchTool = {
name: "search_customer",
description:
"Search customer data once and return structured customer records. Do not ask follow-up questions.",
schema: z.object({ query: z.string() }),
invoke: async ({ query }: { query: string }) => {
const rows = [{ id: "123", name: "John Smith", status: "active" }];
return JSON.stringify({
query,
results: rows,
done: true,
});
},
};
const agent = createReactAgent({
llm,
tools: [searchTool],
});
const result = await agent.invoke(
{
messages: [
{
role: "user",
content:
"Find customer John Smith and summarize his status in one pass.",
},
],
},
{
configurable: {},
recursionLimit: 5,
}
);
The key fix is simple:
- •Make tool outputs deterministic and structured
- •Tell the model when the task is complete
- •Cap recursion/iterations so production fails fast instead of burning tokens
Other Possible Causes
1) Your prompt encourages endless reasoning
If your system prompt says things like “keep using tools until you are absolutely certain,” you’ve basically asked for a loop.
// Bad
const systemPrompt = `
You are an assistant.
Keep using tools until you are completely certain.
Never answer unless all possibilities are checked.
`;
Use bounded instructions instead:
// Better
const systemPrompt = `
Use at most one relevant tool call per step.
If you have enough information, answer directly.
If a tool returns sufficient data, stop and summarize.
`;
2) The model cannot parse your tool output
This often triggers OutputParserException or repeated tool retries when your function returns free-form text instead of valid JSON.
// Bad
return "customer=John Smith; status active; notes maybe escalated";
// Better
return JSON.stringify({
customerId: "123",
name: "John Smith",
status: "active",
});
If you’re using structured tools, keep schema strict:
schema: z.object({
queryIdOrName: z.string().min(1),
})
3) A tool is too broad or non-idempotent
A search tool that returns partial matches can cause repeated refinement calls. Same with write tools that mutate state and then return “success” without an ID.
// Risky
description: "Look up anything related to customers"
// Better
description:
"Lookup exactly one customer by email or ID and return one record only"
For write operations, return explicit confirmation:
return JSON.stringify({
ok: true,
ticketId: "TCK-98121",
});
4) Your executor settings are too loose
In production, default iteration settings can hide bad behavior until traffic spikes.
const executorConfig = {
maxIterations: 15,
earlyStoppingMethod: "generate",
};
If you’re using LangGraph-based agents, also set recursion limits per request:
await agent.invoke(input, {
recursionLimit: 5,
});
How to Debug It
- •
Check whether the same tool is being called repeatedly
- •Log every
tool_call, tool name, and arguments. - •If you see identical calls like
search_customer({ query:"John Smith" })over and over, it’s a loop in prompt or output shape.
- •Log every
- •
Inspect the last few messages before failure
- •Look for repeated assistant messages like:
- •
I need more information - •
Calling search again - •
Let me verify one more thing
- •
- •That means your prompt is nudging the model toward indefinite planning.
- •Look for repeated assistant messages like:
- •
Validate tool output against schema
- •If your agent expects JSON but gets prose, fix the tool first.
- •Add runtime validation before returning from the tool.
- •
Lower iteration limits temporarily
- •Set
maxIterationsorrecursionLimitto something small like3. - •A good workflow should still finish; a bad workflow will fail immediately and reveal the loop source.
- •Set
Prevention
- •
Keep tool contracts narrow:
- •One input shape
- •One output shape
- •One responsibility
- •
Make completion explicit in prompts:
- •“Return a final answer after one successful lookup.”
- •“Do not re-call a tool unless required fields are missing.”
- •
Add guardrails in production:
if (toolCallCount > 3) {
throw new Error("Aborting suspected agent loop");
}
If you’re debugging this in LangChain TypeScript, assume the loop is coming from either your prompt or your tool contract first. Fix those before blaming the framework.
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