From e0f43cc0ef33123fe0839c42a4c10dd1e81f18b4 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 20 May 2026 15:03:38 -0700 Subject: [PATCH 1/2] fix(graph): use SDK in-process ASGI transport for thread metadata writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of the production "all threads Untitled" bug (diagnosed via PR #492's @traceable wrapper): error_type: ConnectError error_message: All connection attempts failed sdk_url: http://localhost:2024 The Python helper was calling get_client(url='http://localhost:2024') when LANGGRAPH_API_URL was unset, then trying to HTTP-call back into the runtime. In local dev this accidentally works because `langgraph dev` listens on 2024. In prod the runtime is on a different port, so every title write threw ConnectError and the bare except swallowed it. Fix: pass `url=os.environ.get("LANGGRAPH_API_URL")` (no fallback). When None, the SDK uses its in-process ASGI transport — the canonical path for graph-to-server self-calls. Docstring excerpt: > If `None`, the client first attempts an in-process connection via > ASGI transport. ... This only works if the client is used from > within the Agent server. Applies to both: - examples/chat/python (canonical demo, where the bug surfaced) - cockpit/chat/threads/python (same anti-pattern, would've failed on prod for the same reason) The @traceable instrumentation from #492 stays — it'll confirm the fix on the next prod probe by surfacing `wrote_title: ` in the LangSmith run output. Co-Authored-By: Claude Opus 4.7 (1M context) --- cockpit/chat/threads/python/src/graph.py | 7 ++++++- examples/chat/python/src/graph.py | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cockpit/chat/threads/python/src/graph.py b/cockpit/chat/threads/python/src/graph.py index 02db531e..52a109f0 100644 --- a/cockpit/chat/threads/python/src/graph.py +++ b/cockpit/chat/threads/python/src/graph.py @@ -44,7 +44,12 @@ async def generate_title(state: MessagesState, config) -> dict: thread_id = (config.get("configurable") or {}).get("thread_id") if not thread_id: return {} - sdk_url = os.environ.get("LANGGRAPH_API_URL", "http://localhost:2024") + # url=None lets the SDK use its in-process ASGI transport when the + # call originates from inside a LangGraph server graph (always the + # case here). The old fallback to localhost:2024 forced an HTTP + # round-trip that fails on the prod runtime container. See PR #492 + # for the diagnosis trail. + sdk_url = os.environ.get("LANGGRAPH_API_URL") try: client = get_client(url=sdk_url) thread = await client.threads.get(thread_id) diff --git a/examples/chat/python/src/graph.py b/examples/chat/python/src/graph.py index fcf52a97..d711cbc9 100644 --- a/examples/chat/python/src/graph.py +++ b/examples/chat/python/src/graph.py @@ -97,7 +97,15 @@ async def _maybe_write_thread_title(state: "State", config: RunnableConfig) -> d """ global _threads_client thread_id = (config.get("configurable") or {}).get("thread_id") - sdk_url = os.environ.get("LANGGRAPH_API_URL", "http://localhost:2024") + # url=None lets the SDK use its in-process ASGI transport when the + # call originates from inside a LangGraph server graph (which is + # always the case here). The old fallback to http://localhost:2024 + # PREVENTED that path: it always forced an HTTP round-trip to a + # port that doesn't exist on the prod runtime container (it does + # locally because `langgraph dev` listens on 2024 — which is why + # this only broke in prod). LANGGRAPH_API_URL is only honoured + # when explicitly set, e.g. for cross-process callbacks. + sdk_url = os.environ.get("LANGGRAPH_API_URL") if not isinstance(thread_id, str) or not thread_id: return {"skipped": "no thread_id in config"} From 42e105ae0d6cc7a0b036b9a23859a9db25b705fc Mon Sep 17 00:00:00 2001 From: Brian Love Date: Wed, 20 May 2026 15:07:03 -0700 Subject: [PATCH 2/2] fix(c-a2ui): same in-process ASGI fix for generate_title node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #474 added a generate_title node to c-a2ui mirroring the examples/chat pattern — including the same broken localhost:2024 fallback. Same fix: pass `url=None` (via unset env) so the SDK uses its in-process ASGI transport. Co-Authored-By: Claude Opus 4.7 (1M context) --- cockpit/chat/a2ui/python/src/graph.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cockpit/chat/a2ui/python/src/graph.py b/cockpit/chat/a2ui/python/src/graph.py index 951dc706..cd974eda 100644 --- a/cockpit/chat/a2ui/python/src/graph.py +++ b/cockpit/chat/a2ui/python/src/graph.py @@ -782,7 +782,11 @@ async def generate_title(state: MessagesState, config) -> dict: thread_id = (config.get("configurable") or {}).get("thread_id") if not thread_id: return {} - sdk_url = os.environ.get("LANGGRAPH_API_URL", "http://localhost:2024") + # url=None lets the SDK use its in-process ASGI transport when the + # call originates from inside a LangGraph server graph (always the + # case here). The old fallback to localhost:2024 forced an HTTP + # round-trip that fails on the prod runtime container. See PR #493. + sdk_url = os.environ.get("LANGGRAPH_API_URL") try: client = get_client(url=sdk_url) thread = await client.threads.get(thread_id)