Xiaobai

Xiaobai

Developer · Builder

Building AI engineering systems, developer tools and long-term digital assets at XBSTACK.

About Xiaobai & XBSTACK →
LangGraph checkpoint round-trip losing ZoneInfo and fold, causing a one-hour DST wall-clock shift after resume

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.

Published · 2026-09-076 min readXBSTACK
#LangGraph#Checkpoint#ZoneInfo#datetime#DST#Python#JsonPlusSerializer#AI Agent

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:

ItemVersion / scope
Test date2026-09-07
macOS26.6.2
Python3.10 venv
LangGraph upstreamcommit 81bf17b23
Checkpoint packagelanggraph-checkpoint 4.2.0 metadata
SerializerJsonPlusSerializer
External models/APIs0

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:

  1. create ZoneInfo("America/New_York");
  2. construct a timezone-aware datetime;
  3. round-trip it through JsonPlusSerializer.dumps_typed() and loads_typed();
  4. compare the timezone object, fold and 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.

LangGraph checkpoint serialization preserves the instant but can lose the original IANA timezone semantics after restore

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.

DST arithmetic can shift the intended wall-clock time by one hour after a LangGraph checkpoint restores a fixed-offset datetime

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 fold still 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.

Timezone-safe LangGraph checkpoint containment stores the instant, IANA zone key and fold before rebuilding the datetime after restore

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:

TestWhat it should prove
Serializer round-tripzone key, fold and instant all survive
DST spring-forwardadding a day does not shift the intended wall clock
DST fall-backambiguous repeated-hour semantics survive
Checkpoint/resume integrationthe 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 ZoneInfo becoming a fixed-offset timezone;
  • fold changed 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.

Topic path / LangGraph

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 →
LangGraph State Lost After Cancel: Why Streaming Output Disappears After StopLangGraph state lost after cancel or Stop? A LangGraph 1.2.9 experiment shows why streamed output can reach the UI before a checkpoint and how to recover safely.LangGraph First Checkpoint Crash: Why an Accepted Run Can DisappearReproduce a LangGraph first-checkpoint crash that leaves zero checkpoints, raises EmptyInputError on resume, and can hide an already accepted background run.LangGraph aupdate_state 'Ambiguous update': Why Async Fails When update_state WorksLangGraph 1.2.11 repro: sync update_state works, but async aupdate_state raises Ambiguous update after a START seed. See the cause and tested explicit as_node workaround.LangGraph Subgraph in Practice: Designing Subgraphs, Worker State, and Local State for Multi-Agent SystemsLangGraph Subgraph in Practice: A practical guide to LangGraph subgraph design, covering parent-child graph boundaries, Worker State isolation, shared state, state propagation.

AI Engineering Weekly

Production changes, real failures, experiments and new XBSTACK assets.

Comments & evidence

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.

Sign-in required Reviewed before public
Loading the discussion…