Skip to content

[langgraph] Command goto does not override existing edges, creates parallel execution (docs unclear) #1239

@luizzappa

Description

@luizzappa

Description

In my use case, I sometimes need to jump to a specific node using a Command with a goto.
I expected that calling goto would ignore the existing edges from the current node and continue execution only from the specified node onward.

Expected behavior:

__START__ -> A -> B -> C -> D -> __END__

// If node A returns Command({ goto: 'C' }), I expected:
__START__ -> A ---goto---> C -> D -> __END__   // B is skipped

However, what actually happens is that the goto does not cancel the existing transition.
Instead, it enqueues the target node in parallel while the normal edge (A -> B) still executes.
The result is a duplicated flow starting from the goto destination:

__START__ -> A  ->   B        -> C -> D -> __END__
                    \
                     ---goto---> C -> D -> __END__

It took quite a while to debug this because it looked like my nodes were being called multiple times before I realized this is how goto works with edges..

If this behavior is intentional, it would be very helpful to clarify this in the documentation. I initially assumed goto was a “jump” command rather than a “branch”.

DX Improvement Ideas

Provide a flag or configuration option to indicate that goto should cancel the current node’s outgoing edges before proceeding.

Minimal Reproducible Example

import { Annotation, Command } from "@langchain/langgraph";

const StateAnnotation = Annotation.Root({
  foo: Annotation<string>({
    reducer: (o, n) => o + "|" + n,
  }),
});

const nodeA = async (state: typeof StateAnnotation.State) => {
  console.log("Called A", state);
  console.log("calling command to go nodeC");
  return new Command({
    update: { foo: "a" },
    goto: "nodeC",
  });
};

const nodeB = async (state: typeof StateAnnotation.State) => {
  console.log("Called B", state);
  return { foo: "b" };
};

const nodeC = async (state: typeof StateAnnotation.State) => {
  console.log("Called C", state);
  return { foo: "c" };
};

const nodeD = async (state: typeof StateAnnotation.State) => {
  console.log("Called D", state);
  return { foo: "d" };
};

import { StateGraph } from "@langchain/langgraph";

export const graph = new StateGraph(StateAnnotation)
  .addNode("nodeA", nodeA, { ends: ["nodeC", "nodeA"] })
  .addNode("nodeB", nodeB)
  .addNode("nodeC", nodeC)
  .addNode("nodeD", nodeD)
  .addEdge("__start__", "nodeA")
  .addEdge("nodeA", "nodeB")
  .addEdge("nodeB", "nodeC")
  .addEdge("nodeC", "nodeD")
  .addEdge("nodeD", "__end__")
  .compile();

Output

Called A { foo: 'i' }
calling command to go nodeC
Called B { foo: 'i|a' }
Called C { foo: 'i|a' }
Called C { foo: 'i|a|b|c' }
Called D { foo: 'i|a|b|c' }
Called D { foo: 'i|a|b|c|c|d' }

Note that both C and D are executed twice.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions