Xiaobai
Developer · Builder
Building AI engineering systems, developer tools and long-term digital assets at XBSTACK.
About Xiaobai & XBSTACK →
LangGraph Checkpoint Loses ZoneInfo and fold: Why DST Can Shift by One Hour After Resume
LangGraph checkpoint one hour off after resume? Independent repro shows ZoneInfo becoming a fixed offset and fold resetting, causing DST wall-clock drift after restore.
LangGraph Checkpoint Loses ZoneInfo and fold: Why DST Can Shift by One Hour After Resume
A LangGraph checkpoint can restore a Python datetime that still looks timezone-aware and even compares equal to the original value while no longer carrying the same time-zone semantics. On September 7, 2026, I independently reproduced the failure shape described in LangGraph upstream issue #8826: an America/New_York ZoneInfo datetime passed through JsonPlusSerializer and came back with a fixed UTC offset instead of the original IANA ZoneInfo; fold=1 also came back as fold=0.
The visible bug appears later. In the fixture, adding one day to 2026-03-07 09:00 New York time before serialization produced the expected 2026-03-08 09:00:00-04:00. Performing the same arithmetic on the restored value produced 2026-03-08 10:00:00-04:00. The checkpoint did not move the absolute instant. It lost the rule that says how New York’s offset changes at the DST boundary, which changed later wall-clock arithmetic.
This article stays deliberately narrow: why a timezone-aware datetime can resume with the wrong local-time behavior in LangGraph, how to reproduce the serializer boundary without a model or database, and what application-level containment is safe to use before a verified upstream release fix exists.
The short answer: the offset survives, the timezone rule does not
The independent repro used:
| Item | Version / scope |
|---|---|
| Test date | 2026-09-07 |
| macOS | 26.6.2 |
| Python | 3.10 venv |
| LangGraph upstream | commit 81bf17b23 |
| Checkpoint package | langgraph-checkpoint 4.2.0 metadata |
| Serializer | JsonPlusSerializer |
| External models/APIs | 0 |
Captured output:
before_tz= zoneinfo.ZoneInfo(key='America/New_York')
after_tz= datetime.timezone(datetime.timedelta(days=-1, seconds=68400))
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
The deceptive line is equal=True. A test that only checks equality can conclude the serializer succeeded. But ZoneInfo("America/New_York") is not just today’s -05:00 offset. It carries an IANA rule set that determines when the zone moves to -04:00, how ambiguous local times are interpreted and how future wall-clock arithmetic should behave.
A fixed datetime.timezone(-05:00) knows only one offset. It does not know that New York changes offset the next day.
So this test is not enough:
assert restored == before
For schedule-sensitive state, a stronger regression should also verify something like:
assert getattr(restored.tzinfo, "key", None) == "America/New_York"
assert restored.fold == before.fold
assert restored + timedelta(days=1) == expected_local_time_next_day
Minimal reproduction: test the serializer before the database
I intentionally did not start with Postgres, SQLite, Redis, Agent Server or a real graph. The public repro repository is:
https://github.com/xbstack/langgraph-zoneinfo-fold-checkpoint-repro
The test only needs four steps:
- create
ZoneInfo("America/New_York"); - construct a timezone-aware
datetime; - round-trip it through
JsonPlusSerializer.dumps_typed()andloads_typed(); - compare the timezone object,
foldand DST-crossing arithmetic.
That isolates the failure boundary. If timezone semantics are already lost at the serializer layer, changing checkpointer backends does not restore information that is no longer in the serialized value.
Run the fixture with:
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python repro/repro.py
The expected final marker is:
REPRODUCED
No API key or model call is involved.

Why this can fail silently in production
If a state field only means “this event happened at 2026-03-07T14:00:00Z”, storing an absolute UTC instant is usually the cleanest design. Losing the original IANA zone is less consequential when the application only compares instants or durations.
The dangerous workloads are wall-clock workloads:
- run every day at 09:00 in New York;
- remind the customer at 08:30 local time tomorrow;
- resume an agent routine at 10:00 local time next Monday;
- use local market or store opening hours;
- move an appointment to the same local time on the next day;
- cut a billing period at local midnight.
Those rules depend on timezone transitions, not on one static offset. A workflow can checkpoint, resume with a valid datetime, continue executing without exceptions and only become wrong when it crosses a DST boundary.
That makes the bug more operationally dangerous than a hard deserialization failure. The object is valid enough to keep the process running.

fold loss is a separate semantic loss
Python’s datetime.fold distinguishes repeated local times during a backward clock transition. A local 01:30 can occur twice when a timezone leaves daylight saving time.
If fold=1 becomes 0 after restore, an application can select the first occurrence when the original business state referred to the second occurrence. That is independent of whether the visible UTC offset looks plausible.
For full wall-clock semantics, a checkpoint regression should therefore ask three separate questions:
- is the IANA timezone key still present?
- is
foldstill present? - does arithmetic after restore follow the same timezone rules?
A tested application-level containment
Until there is a released upstream fix that you have regression-tested, the most controllable boundary is to store the business timezone semantics explicitly rather than relying on object reconstruction.
The public fixed/containment.py fixture stores:
{
"instant": value,
"zone": "America/New_York",
"fold": value.fold,
}
It then rebuilds the value after restore:
def unpack(payload):
instant = payload["instant"]
return instant.astimezone(
ZoneInfo(str(payload["zone"]))
).replace(fold=int(payload["fold"]))
The captured result was:
restored_tz=zoneinfo.ZoneInfo(key='America/New_York')
next_day=2026-03-08 09:00:00-04:00
CONTAINMENT_OK
That demonstrates correct DST-aware wall-clock arithmetic in this fixture after the IANA timezone key is explicitly restored.

It is important not to overstate what this proves. This is an application-level containment, not a LangGraph upstream fix. It does not retroactively repair historical checkpoints and it does not establish that every nested data structure, Pydantic model, dataclass or third-party serializer is safe.
A safer state model: separate instants from local-time rules
I would not make one datetime field carry every time-related meaning in a long-running agent.
For absolute audit or event times, store UTC explicitly:
occurred_at_utc = 2026-03-07T14:00:00Z
For a schedule with local-time semantics, preserve at least:
local_datetime = 2026-03-07 09:00:00
zone = America/New_York
fold = 0
If the system also needs to audit the resolved instant at creation time, keep that as a separate UTC field.
This design makes later decisions explicit. If tzdata changes or a jurisdiction changes DST rules, the application can choose whether to preserve the originally resolved instant or recompute a future wall-clock schedule under the current timezone rules. A serializer should not make that business decision implicitly.
What if old checkpoints only contain a fixed offset?
Do not guess the original IANA timezone from -05:00.
Many timezones can share the same offset on a given date, and one timezone can use different offsets over the year. If the old state only contains:
2026-03-07T09:00:00-05:00
there is not enough information to uniquely infer America/New_York.
A migration has to rely on business evidence that actually exists, such as:
- a timezone stored on the user profile;
- a region attached to the original task;
- an appointment or location record;
- an existing zone field elsewhere in the domain model.
If that evidence was never stored, the correct engineering conclusion is that lossless recovery is impossible. Inventing a zone creates new data corruption.
Regression tests worth adding to checkpoint-sensitive systems
If LangGraph state contains schedules or timezone-aware datetimes, I would add at least these tests:
| Test | What it should prove |
|---|---|
| Serializer round-trip | zone key, fold and instant all survive |
| DST spring-forward | adding a day does not shift the intended wall clock |
| DST fall-back | ambiguous repeated-hour semantics survive |
| Checkpoint/resume integration | the real checkpointer preserves the business schedule |
If the product supports several IANA timezones, include representative regions rather than only UTC.
Most importantly, do not define success as “serialization did not raise.” This issue is specifically a successful deserialization into an object that is semantically incomplete for future wall-clock work.
Upstream status and evidence boundary
As of September 7, 2026, upstream issue #8826 remained open. XBSTACK posted the independent reproduction evidence in the issue thread and published the deterministic repro repository.
The evidence supports these claims:
- the specified upstream commit / checkpoint package metadata reproduced
ZoneInfobecoming a fixed-offset timezone; foldchanged from 1 to 0 in the fixture;- adding one day across the DST boundary changed 09:00 into 10:00 after restore;
- explicitly storing the IANA timezone key and fold restored correct arithmetic in the containment fixture.
The evidence does not support these stronger claims:
- every LangGraph version is affected;
- every checkpointer backend has its own separate bug;
- upstream has accepted a specific fix;
- an unreleased commit can be treated as a supported release;
- historical checkpoints can reconstruct ZoneInfo from a fixed offset with no other data.
If upstream later ships a fix, this page should add a version matrix and rerun the regression fixture. The application-level containment should not be relabeled as the official fix.
The engineering lesson: persist time semantics, not just a Python object
As agent workflows start carrying appointments, reminders, deadlines, routines and market schedules, datetime stops being a simple JSON-like value. An absolute instant, an IANA timezone rule, a local-time schedule and fold are separate pieces of business meaning.
A checkpoint can restore a Python object successfully while still failing to restore those meanings.
The safest current design is straightforward: store absolute events in UTC; store the IANA timezone key explicitly for local-time schedules; preserve fold where ambiguous local times matter; and include DST-crossing arithmetic in checkpoint regression tests.
Repro and containment code: https://github.com/xbstack/langgraph-zoneinfo-fold-checkpoint-repro
For the next step, use the LangGraph hub, the checkpointer / memory / SQLite / Redis guide, or the streaming cancellation vs checkpoint consistency experiment.
Continue through the production LangGraph learning path
The LangGraph hub organizes state isolation, checkpointing, human approval, retries, observability, supervisors, subgraphs and memory into one reviewable path.
More to Explore
Topic hub →AI Engineering Weekly
Production changes, real failures, experiments and new XBSTACK assets.
DISCUSSION
Questions, verification and corrections
Sign in to comment. Every new comment is reviewed before publication; while pending, it is visible only to you and the administrator.