langchain-ai/langchain · critical · RecursionError
Recursion limit reached when invoking {self} with input {val
Error message
Recursion limit reached when invoking {self} with input {value}. What it means
Async twin of the sync recursion check: in `RunnableLambda._ainvoke`, when the wrapped coroutine returns a `Runnable`, it is awaited via `output.ainvoke(...)` with `recursion_limit - 1`. At limit 0 the chain of async runnables-returning-runnables is halted with `RecursionError`. This protects async agent-style pipelines from unbounded delegation.
Source
Thrown at libs/core/langchain_core/runnables/base.py:5278
if output is None:
output = chunk
else:
try:
output = output + chunk # type: ignore[operator]
except TypeError:
output = chunk
else:
output = await acall_func_with_variable_args(
cast("Callable[..., Any]", afunc), value, config, run_manager, **kwargs
)
# If the output is a Runnable, invoke it
if isinstance(output, Runnable):
recursion_limit = config["recursion_limit"]
if recursion_limit <= 0:
msg = (
f"Recursion limit reached when invoking {self} with input {value}."
)
raise RecursionError(msg)
output = await output.ainvoke(
value,
patch_config(
config,
callbacks=run_manager.get_child(),
recursion_limit=recursion_limit - 1,
),
)
return cast("Output", output)
@override
def invoke(
self,
input: Input,
config: RunnableConfig | None = None,
**kwargs: Any | None,
) -> Output:
"""Invoke this `Runnable` synchronously.View on GitHub (pinned to e32fa9a52e)
Solutions
- Make each delegation step strictly consume the input so recursion terminates.
- Increase `recursion_limit` in the config if depth is expected: `await chain.ainvoke(x, {'recursion_limit': 100})`.
- Replace recursive delegation with an explicit `while` loop or a state machine (`RunnableBranch`, LangGraph).
- Log inputs per level to spot non-shrinking inputs.
Example fix
# before
async def route(x):
return handler_runnable # delegates forever on same input
# after
async def route(x):
if x['done']:
return x
return await handler_runnable.ainvoke(x) # consume and finish Defensive patterns
Strategy: validation
Validate before calling
async def ainvoke_bounded(chain, x, max_depth: int = 50):
return await chain.ainvoke(x, config={'recursion_limit': max_depth}) Try / catch
try:
out = await chain.ainvoke(x)
except RecursionError as e:
if 'Recursion limit reached' in str(e):
out = await chain.ainvoke(x, config={'recursion_limit': 100})
else:
raise Prevention
- Make each async delegation step transform/consume its input.
- Prefer explicit loops or LangGraph state machines over recursive runnable returns.
- Bound recursion_limit deliberately in async agent chains.
- Log inputs per delegation level during development.
When it happens
Trigger: An `afunc` (or async `func`) that returns a `Runnable` which again returns a `Runnable`, indefinitely; async routers where the delegate never changes the input; `config={'recursion_limit': n}` smaller than the delegation depth.
Common situations: Async agent loops built from `RunnableLambda` routing to the next step; deep async chains in FastAPI handlers; migrating sync recursion-limited code to async and re-hitting the limit.
Related errors
- Recursion limit reached when invoking {self} with input {inp
- Cannot invoke a coroutine function synchronously.Use `ainvok
- Recursion limit reached when invoking {self} with input {fin
- Cannot stream from a generator function asynchronously.Use .
- {self!r} only supports sync methods.
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/0b4682a2909d1a41.
Report an issue: GitHub.