DEV Community

Cover image for LangGraph Checkpoint Loses ZoneInfo and fold: Reproducing a One-Hour DST Shift
xn
xn

Posted on Originally published at xbstack.com

LangGraph Checkpoint Loses ZoneInfo and fold: Reproducing a One-Hour DST Shift

A checkpoint can restore a Python datetime that still compares equal to the original and yet behave differently the next time your workflow crosses a DST boundary. I reproduced that failure shape against LangGraph upstream commit 81bf17b23 with langgraph-checkpoint 4.2.0 metadata.

The surprising part is not a crash. The serializer succeeds. The instant survives. What disappears is the IANA timezone rule and the fold bit.

Minimal reproduction

Python 3.10, JsonPlusSerializer, no model calls, no database, no LangSmith:

before_tz= zoneinfo.ZoneInfo(key='America/New_York')
after_tz= datetime.timezone(... -05:00)
equal= True
before_plus1= 2026-03-08 09:00:00-04:00
after_plus1= 2026-03-08 10:00:00-04:00
fold= 1 -> 0
Result: REPRODUCED
Enter fullscreen mode Exit fullscreen mode

Same instant, lost timezone semantics

equal=True is exactly why a normal round-trip test can miss the bug. A fixed UTC-05:00 offset can represent the same instant as ZoneInfo("America/New_York"), but it does not know that New York switches to UTC-04:00 the next day.

Why +1 day becomes one hour wrong

The fixture starts at 2026-03-07 09:00 in New York and adds one day across the spring DST transition. Before serialization the wall clock remains 09:00. After restore the fixed-offset datetime produces 10:00.

DST arithmetic shift after checkpoint restore

This matters for reminders, appointments, market schedules, billing cutoffs and long-running agent routines. An absolute event stored in UTC is much less exposed.

fold is a separate piece of state

Python's fold distinguishes the two copies of an ambiguous local time when clocks move backward. In the same round trip, fold=1 became fold=0. A robust regression therefore checks more than equality:

assert getattr(restored.tzinfo, "key", None) == "America/New_York"
assert restored.fold == before.fold
assert restored + timedelta(days=1) == expected_local_time_next_day
Enter fullscreen mode Exit fullscreen mode

A containment pattern that passed locally

Until a released upstream fix is regression-tested in your stack, persist timezone semantics explicitly:

payload = {
    "instant": value,
    "zone": "America/New_York",
    "fold": value.fold,
}
Enter fullscreen mode Exit fullscreen mode

Then rebuild with ZoneInfo after restore. The fixture returned:

restored_tz=zoneinfo.ZoneInfo(key='America/New_York')
next_day=2026-03-08 09:00:00-04:00
CONTAINMENT_OK
Enter fullscreen mode Exit fullscreen mode

Store instant, zone key and fold

This is application-level containment, not an upstream LangGraph fix. It cannot reconstruct an IANA zone from an old fixed offset if the original zone was never stored.

Practical rule

Use UTC for absolute events. For schedules with local-time meaning, persist the IANA timezone key explicitly, preserve fold when ambiguous times matter, and include DST-crossing arithmetic in checkpoint regression tests.

Repro: github.com/xbstack/langgraph-zoneinfo-fold-checkpoint-repro.


Original XBSTACK article: https://www.xbstack.com/en/ai/langgraph-checkpoint-zoneinfo-fold-dst/?utm_source=devto&utm_medium=referral&utm_campaign=langgraph_zoneinfo_fold_checkpoint&utm_content=original

Related:

Top comments (0)