LangSmith UI stuck in router node when not not invoking SubGraph from router node

This is the code at a high level. I am using LangGraph with a router node that uses `Command` to route execution to `END`, `planner`, `tools`, or invoke a subgraph from main graph

# ===============================
# Router Node
# ===============================
  async def router_node(state: MainGraphState) -> Command[Literal["tools", "planner", "__end__"]]:
      print("-" * 30 + "ENTERING ROUTER NODE " + "-" * 30)
      messages = state["messages"]
      last_message = messages[-1]

      if len(last_message.tool_calls) == 0: 
          print("✅ No tool calls detected, finishing execution.")
          return Command(goto=END)

      # Current implementation handles only one tool call per message
      tool_call = last_message.tool_calls[0]
      if tool_call["name"] == "subgraph_dispatch":
          tool_name = tool_call["args"]["subgraph_name"]
          tool_query = tool_call["args"]["query"]
          print(f"🔍 Routing to subgraph based on tool call: {tool_name} with query: {tool_query}\n")
          if tool_name == SubgraphName.SEARCH.value:
              result = await search_subgraph.ainvoke({"local_messages": [HumanMessage(content=tool_query)]})
              response = result["local_messages"][-1].content
              return Command(update={"messages": [ToolMessage(content=response,tool_call_id=tool_call["id"])]}, goto="planner")
          else:
              return Command(update={"messages": [ToolMessage(content="Subgraph not found")]}, goto="planner")
      else:
          return Command(goto="tools")

As shown in the video, In langSmith Studio’s Interact, the flow appears to return from the subgraph’s end to the main graph’s planner or ToolNode or end.

Due to this, when the AI agent does not make a tool call to subgraph, the execution appears to get stuck in the Interact and Trace views. However, the terminal running langgraph dev shows the expected behavior: the execution enters the “no tool calls” branch, exits the router node and terminates successfully.

@Aashishkumar-07 Would love to help you out, but your link returns 404. Also it is possible for you to share the Langsmith trace.

Hey, sorry about that.

Here is the LangSmith trace and video.

If I have not understood wrong, this issue seems to be not there for me, but the implementation for solving the issue might have caused unexpected behavior. I am planning to expose the subgraph as tool rather than using tool as en empty pass and conditionally handling it in router_node and invoking the subgraph.

But was curious, why was it not working in langSmith trace since it is the mentioned way in langGraph documents for different state schemas.

Hey, My post is getting flagged as spam, will investigate why and will share

It’s strange why your post is getting spammed, but from the evidence that you have shared , this looks like a LangSmith Studio visualization issue, not a graph execution bug: your terminal shows the router correctly taking the no-tool-calls branch and reaching END, while Studio’s Interact view stays on the router.

A few doc-backed notes that might be helpful:

  1. Your current pattern is valid for different state schemas. LangGraph’s recommended approach when parent/subgraph states don’t share keys is to invoke/ainvoke the subgraph inside a node and map state back , see Call a subgraph inside a node. So this isn’t an anti-pattern per se.

  2. Studio can get confused with Command + nested subgraph work. Make sure your router node:

  1. Your plan to expose the subgraph as a tool aligns with the recommended multi-agent subagents pattern and per-invocation subgraph docs. Tradeoff: tool-wrapped subgraphs aren’t statically discoverable for get_state(subgraphs=True), see View subgraph state.

  2. If you want built-in subagent orchestration, Deep Agents’ create_deep_agent(subagents=[...]) wires up a task tool via SubAgentMiddleware, but that’s optional; plain LangGraph tool-wrapping works too.

Could you share your langgraph / langgraph-api versions and whether the router node has any static edges in addition to Command returns? That would help confirm if this is a known Studio rendering edge case.

Hey @Aashishkumar-07, just following up if you were able to have a look and possibly share your langgraph / langgraph-api versions?

Hey, I was out of station, so I was not able to follow up sooner.

Here are the versions I am using:

Name: langchain
Version: 1.2.17

Name: langgraph
Version: 1.1.10

These are all the edges in my graph. There is no static edge originating from the router node but there is one static edge towards the router node.

graph.add_node("planner", planner)
graph.add_node("router", router_node)
graph.add_node("tools", tool_node)

graph.add_edge("planner", "router")
graph.add_edge("tools", "planner")

Hello @Aashishkumar-07, since you do not have static edges in router node that rules this cause out.
Can you please upgrade LangGraph to v1.2.7 and LangChain to 1.3.1.1 and try again?

Hi,

I have upgraded to the latest versions, but I am still observing the same behavior.

Name: langgraph
Version: 1.2.7

Name: langchain
Version: 1.3.11

Also, after revisiting both my earlier and newer LangSmith Trace , I observed that the output has appeared correctly. The planner node is the starting node of my graph, so the trace naturally starts there. Similarly, the router node returns Command(goto=END), so the trace ends at the router node.

The interpretation that the execution was getting “stuck” at the router node arose because of the following reasons:

  1. Due to LangSmith visualization : As shown in video, when a subgraph is invoked, the flow appears to return from the subgraph’s END node back to the main graph’s planner, ToolNode or END. In my case, the router node did not invoke a subgraph (e.g., for a weather query where no tool call is made), it directly returns Command(goto=END) in the main graph.
    Since there was no visual transition for this execution path, I interpreted the execution as being stuck. As per the code shared, the subgraph’s END always transitions back to the main graph’s planner, whereas the router node can transition to planner, ToolNode, or END without invoking a subgraph. I think the visualization could be clearer for this execution path.

  2. The planner is configured as the graph’s entry point (graph.set_entry_point("planner")), while END is represented as a terminal state. Since start node, planner node, router node appeared in interact, I expected a visible END node to appear in the interact and that expectation also contributed to my misunderstanding.

Thank you for your time.

@Aashishkumar-07 Excellent, if everything is clear, then let’s close this thread by marking the answer as a solution.

@keenborder786 , The application executes correctly, but as mentioned before in point 1, I believe the visualization is not clear for this execution path. When the router returns Command(goto=END) without invoking a subgraph, the graph does not clearly show the transition to the parent graph’s END. Because of this, I initially interpreted the execution as being stuck at the router. Is this an expected limitation of the current visualization or should this path be represented more explicitly?

Should I keep this thread open to see if anyone else has encountered the same issue?

@Aashishkumar-07 Thanks for the follow-up and for sharing the diagram, that helps clarify things.

Your graph is executing correctly. When router returns Command(goto=END) with no tool calls, the run terminates as expected. Your terminal output and LangSmith Trace confirm this. The trace ending at router is also expected, since END is a terminal state, not a runnable node you “visit” the way you do planner or tools.

To answer your question directly:

Is this an expected limitation of the current visualization?
Yes. This is a Studio visualization/Interact UX limitation, not a graph execution bug. Your Command[Literal["tools", "planner", "__end__"]] annotation is the correct way to declare possible Command destinations for static graph rendering (Graph API docs). Your diagram reflects that for the subgraph-completion case, the dashed edges from the subgraph’s __end__ back to planner, tools, and __end__.

The gap appears when router returns Command(goto=END) without invoking the subgraph. In that case, execution never enters the subgraph internals, so Interact doesn’t show the same enter → exit → return animation you see when the subgraph runs. Interact can look idle on router even though the run has finished.

Should this path be represented more explicitly?
For the static graph diagram, not necessarily. LangGraph already infers possible Command destinations from your Command[Literal["tools", "planner", "__end__"]] annotation. Drawing every runtime branch explicitly, including a direct router → __end__ path alongside the subgraph exit paths, could make the diagram more complex without adding much clarity.

For Interact, though, a clearer completion signal when Command(goto=END) fires without invoking the subgraph would help. That wouldn’t require more edges on the diagram, just better runtime feedback so it doesn’t look stuck on router when the run has actually finished.

So: your graph design is fine; the static visualization tradeoff is reasonable; the Interact UX for this specific branch could still be improved.

I hope this clarifies everything.