Feature: should get type errors when user tries to compile StateGraph that is incomplete #168

Closed
opened 2026-02-15 17:16:41 -05:00 by yindo · 2 comments
Owner

Originally created by @benjamincburns on GitHub (Feb 10, 2025).

Problem

This code is incorrect:

...
const graphBuilder = new StateGraph(...);
graphBuilder.addNode("foo", fooNode);
graphBuilder.addEdge(START, "foo");
graphBuilder.addEdge("foo", END);

const graph = graphBuilder.compile({ checkpointer });

The correct code is:

const graphBuilder = new StateGraph(...)
  .addNode("foo", fooNode)
  .addEdge(START, "foo")
  .addEdge("foo", END);

const graph = graphBuilder.compile({ checkpointer });

The first example is incorrect because addNode and addEdge don't mutate graphBuilder.

I suspect this crops up somewhat frequently, as we recently had this issue in an example in our docs (fixed in #854).

Proposed solution

Can we make it a type error to call compile on an incomplete graph? I suspect that this may be difficult, as you'd need to find a way to express the "completeness" of the graph in terms of TS type constraings. If it's not possible (or exceptionally difficult or cumbersome to implement), can we at least make it a type error to call compile on an empty graph? That would help users catch this issue earlier.

Alternatively, perhaps we should consider making addNode and addEdge mutate. I personally prefer the immutable design, but perhaps mutation is more user-friendly?

Originally created by @benjamincburns on GitHub (Feb 10, 2025). ## Problem This code is incorrect: ```typescript ... const graphBuilder = new StateGraph(...); graphBuilder.addNode("foo", fooNode); graphBuilder.addEdge(START, "foo"); graphBuilder.addEdge("foo", END); const graph = graphBuilder.compile({ checkpointer }); ``` The correct code is: ```typescript const graphBuilder = new StateGraph(...) .addNode("foo", fooNode) .addEdge(START, "foo") .addEdge("foo", END); const graph = graphBuilder.compile({ checkpointer }); ``` ~~The first example is incorrect because `addNode` and `addEdge` don't mutate `graphBuilder`.~~ I suspect this crops up somewhat frequently, as we recently had this issue in an example in our docs (fixed in #854). ## Proposed solution Can we make it a type error to call `compile` on an incomplete graph? I suspect that this may be difficult, as you'd need to find a way to express the "completeness" of the graph in terms of TS type constraings. If it's not possible (or exceptionally difficult or cumbersome to implement), can we at least make it a type error to call `compile` on an empty graph? That would help users catch this issue earlier. Alternatively, perhaps we should consider making `addNode` and `addEdge` mutate. I personally prefer the immutable design, but perhaps mutation is more user-friendly?
yindo added the good first issuehelp wanted labels 2026-02-15 17:16:41 -05:00
yindo closed this issue 2026-02-15 17:16:41 -05:00
Author
Owner

@zkpranav commented on GitHub (Feb 22, 2025):

I believe addNode and addEdge do mutate graphBuilder. They return this explicitly casting it into an updated type.
For example, this runs just fine (on 0.2.48).

const StateAnnotation = Annotation.Root({
    x: Annotation<number>,
});

const node = (state: typeof StateAnnotation.State) => {
    return {
        x: state.x + 1,
    };
};

const graphBuilder = new StateGraph(StateAnnotation);
graphBuilder.addNode("f", node);
graphBuilder.addEdge(START, "f" as any);

console.log((await graphBuilder.compile().invoke({ x: 0 })).x);

And so does this without the explicit cast to any.

...
const graphBuilder = new StateGraph(StateAnnotation);
graphBuilder.addNode("f", node).addEdge(START, "f");
...

The incompatible type warning is because the initial type inferred by graphBuilder is never updated after subsequent calls to addNode.

@zkpranav commented on GitHub (Feb 22, 2025): I believe `addNode` and `addEdge` do mutate `graphBuilder`. They return `this` explicitly casting it into an updated type.\ For example, this runs just fine (on `0.2.48`). ``` const StateAnnotation = Annotation.Root({ x: Annotation<number>, }); const node = (state: typeof StateAnnotation.State) => { return { x: state.x + 1, }; }; const graphBuilder = new StateGraph(StateAnnotation); graphBuilder.addNode("f", node); graphBuilder.addEdge(START, "f" as any); console.log((await graphBuilder.compile().invoke({ x: 0 })).x); ``` And so does this without the explicit cast to `any`. ``` ... const graphBuilder = new StateGraph(StateAnnotation); graphBuilder.addNode("f", node).addEdge(START, "f"); ... ``` The incompatible type warning is because the initial type inferred by `graphBuilder` is never updated after subsequent calls to `addNode`.
Author
Owner

@benjamincburns commented on GitHub (Feb 23, 2025):

Ah thanks for the correction - I should've looked before I posted that

@benjamincburns commented on GitHub (Feb 23, 2025): Ah thanks for the correction - I should've looked before I posted that
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraphjs#168