langchain-ai / langchain-ai/langgraph
config typing warning recommends 'RunnableConfig | None', but that exact annotation triggers the warning (under PEP 563) and silently disables config injection
- Dominant language
- Python
- Stars
- 41.8k
- Forks
- 7.1k
- Avg merge
- 23h 7m
- Merged PRs (30d)
- 30
Description
### Checked other resources
- [x] This is a bug, not a usage question.
- [x] I added a clear and descriptive title that summarizes this issue.
- [x] I used the GitHub search to find a similar question and didn't find it.
- [x] I am sure that this is a bug in LangGraph rather than my code.
- [x] The bug is not resolved by updating to the latest stable version of LangGraph (or the specific integration package).
- [x] This is not related to the langchain-community package.
- [x] I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.
### Related Issues / PRs
* #5787 — added this warning
* #5798 — implementation PR (interesting detail: the agent's plan text used the message `'RunnableConfig' or 'Optional[RunnableConfig]'`, which matches the accept-list; the merged code says `'RunnableConfig | None'`, which does not)
### Reproduction Steps / Example Code (Python)
```python
from __future__ import annotations # PEP 563 — annotations become raw strings
import warnings
from typing_extensions import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import END, START, StateGraph
class State(TypedDict):
seen: list
def node(state: State, config: RunnableConfig | None = None) -> dict:
# This is the spelling the warning message itself recommends.
return {"seen": [config]}
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
builder = StateGraph(State)
builder.add_node("node", node)
builder.add_edge(START, "node")
builder.add_edge("node", END)
graph = builder.compile()
for w in caught:
print("WARNING:", w.message)
result = graph.invoke({"seen": []}, config={"tags": ["probe"]})
print("injected config:", result["seen"][0])
print("probe tag reached node:", result["seen"][0] is not None and "probe" in result["seen"][0].get("tags", []))
```
### Error Message and Stack Trace (if applicable)
```shell
$ python repro.py
WARNING: The 'config' parameter should be typed as 'RunnableConfig' or 'RunnableConfig | None', not 'RunnableConfig | None'.
injected config: None
probe tag reached node: False
```
No exception is raised — the failure is silent: the node receives `config=None` on every invoke, so the callbacks/tracing chain never reaches anything the node calls.
### Description
I'm typing a node's injected `config` parameter as `RunnableConfig | None = None` (in a module using `from __future__ import annotations`, a widely used modern default).
I expect: per the warning message's own recommendation, this spelling is accepted — no warning, and `config` is injected.
What happens instead: the warning fires with a self-referential message (`... should be typed as 'RunnableConfig' or 'RunnableConfig | None', not 'RunnableConfig | None'`), **and config injection is silently disabled** — the node runs with `config=None` on every invoke. We hit this in production: all LLM calls made inside such a node were invisible to Langfuse, because `config` never carried the callback manager into them.
**Root cause** (`langgraph/_internal/_runnable.py`, `RunnableCallable.__init__`): the check compares the RAW `inspect.signature` annotation against the `KWARGS_CONFIG_KEYS` accept entries `(RunnableConfig, "RunnableConfig", Optional[RunnableConfig], "Optional[RunnableConfig]", empty)`. Under PEP 563 the annotation is the string `"RunnableConfig | None"`, which is not in the list, so the branch warns and `continue`s — skipping the `func_accepts["config"]` registration. There is no stringified union entry, and the message recommends exactly the spelling the list rejects.
Verified matrix (langgraph 1.2.11, Python 3.13.12):
| annotation on `config` | `from __future__ import annotations` | no future import (real objects) |
|---|---|---|
| `RunnableConfig \| None` | warns + injection disabled | accepted (only via PEP 604 equality with the `Optional[RunnableConfig]` entry) |
| `None \| RunnableConfig` | warns + injection disabled | accepted |
| `RunnableConfig\|None` (no spaces) | warns + injection disabled | — |
| aliased import `RC \| None` | warns + injection disabled | — |
| bare `RunnableConfig` | accepted | accepted |
| `Optional[RunnableConfig]` | accepted | accepted |
| no annotation | accepted (injection happens) | accepted |
Additional notes:
* The `KWARGS_CONFIG_KEYS` docstring on `main` already acknowledges this class of gap: "This is fully internal and should be further refactored to use `get_type_hints` to resolve forward references and optional types formatted like BaseStore | None." The `store` entries have the same gap.
* The test added with the warning (`tests/test_deprecation.py::test_config_parameter_incorrect_typing`) doesn't cover the future-import + `X | None` case, which is how this shipped.
* Ecosystem projects already suppress this exact warning message in their test suites (e.g. deepeval's langgraph integration tests use `filterwarnings` on it), which masks the silent injection loss.
* Checked today: latest stable (1.2.11) and `main` both still have the unchanged accept-list.
**Suggested fix:** resolve annotations via `typing.get_type_hints(func)` before matching (falling back to the raw annotation if resolution fails, e.g. unresolvable forward refs), or minimally add the stringified `"RunnableConfig | None"` / `"BaseStore | None"` forms to the accept entries and align the warning message with whatever the list actually accepts. Separately worth considering: silently disabling injection is a functional change (callbacks/tracing/configurable all dropped) — arguably it deserves a louder failure mode than a `UserWarning` that recommends a spelling that doesn't work.
### System Info
```shell
System Information
------------------
> OS: Linux
> OS Version: #149~20.04.1-Ubuntu SMP Wed Apr 16 08:29:56 UTC 2025
> Python Version: 3.13.12 (main, Feb 12 2026, 00:45:41) [Clang 21.1.4 ]
Package Information
-------------------
> langchain_core: 1.5.4
> langchain: 1.3.15
> langsmith: 0.10.18
> deepagents: 0.7.6
> langchain_anthropic: 1.5.6
> langchain_google_genai: 4.3.3
> langchain_openai: 1.1.11
> langchain_protocol: 0.0.18
> langgraph_sdk: 0.4.2
Optional packages not installed
-------------------------------
> deepagents-cli
Other Dependencies
-------------------
> langgraph: 1.2.11
```
Contributor guide
Research direction
Start in langgraph/_internal/_runnable.py at RunnableCallable.__init__ and inspect how KWARGS_CONFIG_KEYS is matched against annotations. Run the reproduction and review tests/test_deprecation.py::test_config_parameter_incorrect_typing, then add coverage for the future-import and RunnableConfig | None case. Done means the warning no longer contradicts its recommendation and config injection remains available for the reproduced annotation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100