langchain-ai/langchain · critical · RecursionError
Recursion limit reached when invoking {self} with input {fin
Error message
Recursion limit reached when invoking {self} with input {final}. What it means
In `RunnableLambda._transform` (sync streaming), if the function's output is itself a `Runnable`, the lambda streams from that runnable with `recursion_limit - 1`; at limit 0 a `RecursionError` is raised. This bounds sync streaming pipelines where lambdas keep returning runnables, mirroring the invoke-path guard.
Source
Thrown at libs/core/langchain_core/runnables/base.py:5393
output = chunk
else:
try:
output = output + chunk
except TypeError:
output = chunk
else:
output = call_func_with_variable_args(
self.func, final, config, run_manager, **kwargs
)
# If the output is a Runnable, use its stream output
if isinstance(output, Runnable):
recursion_limit = config["recursion_limit"]
if recursion_limit <= 0:
msg = (
f"Recursion limit reached when invoking {self} with input {final}."
)
raise RecursionError(msg)
for chunk in output.stream(
final,
patch_config(
config,
callbacks=run_manager.get_child(),
recursion_limit=recursion_limit - 1,
),
):
yield chunk
elif not inspect.isgeneratorfunction(self.func):
# Otherwise, just yield it
yield cast("Output", output)
@override
def transform(
self,
input: Iterator[Input],
config: RunnableConfig | None = None,View on GitHub (pinned to e32fa9a52e)
Solutions
- Terminate delegation: invoke/absorb the returned runnable inside the lambda instead of returning it.
- Raise `recursion_limit`: `chain.stream(x, config={'recursion_limit': 100})`.
- Model the hand-off as an explicit sequence or loop rather than nested runnable returns.
- Verify each level's output type — only return a `Runnable` when you intend transparent delegation.
Example fix
# before fn = RunnableLambda(lambda chunks: (yield next_runnable)) # yields a Runnable # after fn = RunnableLambda(lambda chunks: (yield from next_runnable.stream(consume(chunks))))
Defensive patterns
Strategy: validation
Validate before calling
def stream_bounded(chain, x, max_depth: int = 50):
yield from chain.stream(x, config={'recursion_limit': max_depth}) Try / catch
try:
for chunk in chain.stream(x):
yield chunk
except RecursionError as e:
if 'Recursion limit reached' in str(e):
yield from chain.stream(x, config={'recursion_limit': 100})
else:
raise Prevention
- Yield data chunks, not Runnable objects, from streaming lambdas.
- Stream from inner runnables explicitly rather than returning them.
- Bound recursion_limit for streaming chains with known depth.
When it happens
Trigger: A generator-function lambda yielding a `Runnable` as a chunk; `stream()` on a chain whose lambda returns a runnable that returns a runnable, deeper than `recursion_limit`; explicit low `recursion_limit` in streaming config.
Common situations: Streaming routers that hand off to the next runnable per chunk; migrating invoke-based delegation code to `stream()` and keeping infinite hand-off; deep streaming chains exceeding the default limit.
Related errors
- Recursion limit reached when invoking {self} with input {inp
- Recursion limit reached when invoking {self} with input {val
- Cannot stream a coroutine function synchronously.Use `astrea
- Cannot stream from a generator function asynchronously.Use .
- Expected a generator function type for `transform`.Instead g
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/bd78a621ca6d6cfe.
Report an issue: GitHub.