fix(langgraph): allow providing both context and configurable in RemoteGraph, fixes #6342
Description:
Fixes https://github.com/langchain-ai/langgraph/issues/6342
This PR fixes a 400 error in RemoteGraph when using both context and configurable together. It merges any configurable fields into context internally and removes the configurable key, allowing checkpointing and middleware to work under the context-first architecture introduced in LangGraph 1.0.
Summary of Issue
This PR resolves an issue where using both context and configurable together in RemoteGraph caused a 400 Bad Request error:
Cannot specify both configurable and context. Prefer setting context alone.
Context was introduced in LangGraph 0.6.0 and is the long term planned replacement for configurable.
The 400 error here isn’t about using both config and context, it’s specifically about using both configurable and context together. In other words, this line in the error:
Cannot specify both configurable and context.
refers to the configurable field inside config, not the entire config argument. You can safely pass both config and context as long as config doesn’t contain a configurable key. So, instead of doing this:
config = {"configurable": {"thread_id": thread_id}}
context = {"user_id": "123"}
You can move those fields up into your context:
context = {"user_id": "123", "thread_id": thread_id}
and omit the configurable block entirely.
However, with this approach, checkpointing still doesn’t work, since the thread id being sent to the langgraph api comes from the config[”configurable”], not the context.
Fix Summary
- Added an explicit
contextparameter toRemoteGraph.stream(),.astream(),.invoke(), and.ainvoke(). - Unified
configurablefields intocontextinternally to prevent 400 errors from the API. - Ensures checkpointing continues to work when using
context. - Aligns RemoteGraph API with LangGraph 1.0’s context-first architecture.
Correct Usage
Previously, this would fail because you can’t specify both configurable and context:
config = {"configurable": {"thread_id": "123"}}
context = {"user_id": "abc"}
remote_graph.ainvoke(input, config=config, context=context)
Now, you can simply do:
context = {"user_id": "abc", "thread_id": "123"}
config = {
"run_name": "joke_generation",
"tags": ["humor", "demo"],
} # remove configurable key from config
remote_graph.ainvoke(input, config=config, context=context)
Works because config does not contain a configurable key.
Everything that used to go inside configurable can now be placed directly in context.
Backward Compatibility
While it’s still technically possible to pass both configurable and context together, it’s not recommended. Internally, the configurable block is merged into context and removed to prevent API errors. This ensures existing workflows do not break while promoting the context-first model introduced in LangGraph 0.6.0.
Implementation Highlights
if context:
context.update(sanitized_config["configurable"])
sanitized_config.pop("configurable", None)
async for chunk in client.runs.stream(
thread_id=context.get("thread_id")
if context
else sanitized_config["configurable"].get("thread_id"),
assistant_id=self.assistant_id,
input=input,
command=command,
config=sanitized_config,
context=context,
stream_mode=stream_modes,
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
stream_subgraphs=subgraphs or stream is not None,
if_not_exists="create",
headers=(
_merge_tracing_headers(headers) if self.distributed_tracing else headers
),
params=params,
**kwargs,
):
...
- Merges
configurablefields intocontextbefore sending the request. - Removes
configurablefromconfigto prevent API rejection. - Supports checkpointing and middleware under the unified context interface.
X handle - https://x.com/bolexyro
@bolexyro is attempting to deploy a commit to the LangChain Team on Vercel.
A member of the Team first needs to authorize it.