{"record":{"id":"54fa20b362a4ebdb","repo":"langchain-ai/langchain","slug":"recursion-limit-reached-when-invoking-self-with","errorCode":null,"errorMessage":"Recursion limit reached when invoking {self} with input {input_}.","messagePattern":"Recursion limit reached when invoking (.+?) with input (.+?)\\.","errorType":"exception","errorClass":"RecursionError","httpStatus":null,"severity":"critical","filePath":"libs/core/langchain_core/runnables/base.py","lineNumber":5178,"sourceCode":"                if output is None:\n                    output = chunk\n                else:\n                    try:\n                        output = output + chunk  # type: ignore[operator]\n                    except TypeError:\n                        output = chunk\n        else:\n            output = call_func_with_variable_args(\n                self.func, input_, config, run_manager, **kwargs\n            )\n        # If the output is a Runnable, invoke it\n        if isinstance(output, Runnable):\n            recursion_limit = config[\"recursion_limit\"]\n            if recursion_limit <= 0:\n                msg = (\n                    f\"Recursion limit reached when invoking {self} with input {input_}.\"\n                )\n                raise RecursionError(msg)\n            output = output.invoke(\n                input_,\n                patch_config(\n                    config,\n                    callbacks=run_manager.get_child(),\n                    recursion_limit=recursion_limit - 1,\n                ),\n            )\n        return cast(\"Output\", output)\n\n    async def _ainvoke(\n        self,\n        value: Input,\n        run_manager: AsyncCallbackManagerForChainRun,\n        config: RunnableConfig,\n        **kwargs: Any,\n    ) -> Output:\n        if hasattr(self, \"afunc\"):","sourceCodeStart":5160,"sourceCodeEnd":5196,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/runnables/base.py#L5160-L5196","documentation":"In `RunnableLambda._invoke`, if the wrapped function returns a `Runnable`, the lambda transparently invokes it, decrementing `config['recursion_limit']` each level. When the limit reaches 0, a `RecursionError` is raised to stop unbounded (or accidentally infinite) runnables-returning-runnables chains. This is the same mechanism that halts runaway LangGraph-style loops built purely from runnables.","triggerScenarios":"A lambda that returns a runnable chosen dynamically (`RunnableLambda(lambda x: route(x).invoke(...)` style self-delegation without consuming input); mutually delegating runnables; `config={'recursion_limit': 0 or small N}` with N levels of nested runnable-returning lambdas; long agent loops composed of `RunnableLambda`s.","commonSituations":"Building routers/agents where each lambda returns the next runnable and input never changes, creating an infinite chain; legitimately deep pipelines (dozens of nested runnable returns) hitting the default limit (25); users lowering `recursion_limit` to fail fast.","solutions":["Break the cycle: ensure the delegated invocation consumes/transforms input so recursion terminates (e.g. return `output.invoke(x)` result, not the runnable chain itself).","Raise the limit if the depth is legitimate: `runnable.invoke(x, config={'recursion_limit': 100})`.","Restructure mutual delegation into an explicit loop or `RunnableSequence` instead of nested returns.","Debug by printing the lambda inputs at each level to detect non-terminating delegation."],"exampleFix":"// before\nstep = RunnableLambda(lambda x: x + 1)\nloop = RunnableLambda(lambda x: step)  # returns a Runnable -> infinite delegation\n\n// after\nstep = RunnableLambda(lambda x: x + 1)\nloop = RunnableLambda(lambda x: step.invoke(x, config) if isinstance(x, int) else x)\n// better: express the pipeline directly\nchain = step | step | step","handlingStrategy":"validation","validationCode":"def invoke_bounded(chain, x, max_depth: int = 50):\n    return chain.invoke(x, config={'recursion_limit': max_depth})\n\n# and ensure lambdas never re-return a Runnable for identical input:\ndef terminates(fn, sample) -> bool:\n    out = fn(sample)\n    return not isinstance(out, type(None))","typeGuard":null,"tryCatchPattern":"try:\n    out = chain.invoke(x)\nexcept RecursionError as e:\n    if 'Recursion limit reached' in str(e):\n        out = chain.invoke(x, config={'recursion_limit': 100})  # only if depth is legitimate\n    else:\n        raise","preventionTips":["Never write a lambda that returns a Runnable for the same input it received.","Absorb delegated runnable calls inside the lambda body.","Set recursion_limit explicitly when deep delegation is by design.","Add termination tests for router-style lambdas."],"tags":["runnable","runnable-lambda","recursion","infinite-loop","config"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}