# Refreshing event stream using langchain/react use stream replays old checkpoints

**URL:** <https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341>\
**Category:** LangGraph\
**Tags:** js-help\
**Created:** [August 12, 2026, 2:35pm UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341 "2026-08-12T14:35:30Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![reecemillsom](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/reecemillsom/32/963_2.png) [@reecemillsom](https://forum.langchain.com/u/reecemillsom)\
**Post date:** [August 12, 2026, 2:35pm UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341/1 "2026-08-12T14:35:30Z")

</div>

# Support request: v2 protocol event replay on reconnect/first-subscribe degrades UI state (self-hosted LangGraph + `@langchain/react` `useStream`)

## Description

We’re migrating our chat frontend to `@langchain/react`’s `useStream` (v2 streaming protocol) against a self-hosted LangGraph Platform deployment, and we’re seeing a family of problems that all trace back to the same protocol behaviour: **the server replays the thread’s full buffered event history to every newly-opened subscription** , and the client has no reliable way to distinguish that replay from live events.

The reason we are noticing this problem is we are trying to migration to `@lanchain/react`.

The user-visible impact on a page refresh of an existing thread:

1. **State rewind:** on the first `submit()` after a refresh (the deferred root pump’s first open), the replay re-emits old runs’ `values` snapshots. Each one replaces the projected root state, so our UI visibly reverts — chat title back to its initial value, task sections and artefacts/sidebar collapsing — before walking forward through history to the current state.
2. **Loading/“thinking” indicator flicker and premature settle:** replayed **terminal lifecycle events from prior runs** settle the loading state of the _newly submitted_ run and trigger repeated subscription pause/resume cycles. On video we can watch the thinking indicator appear, vanish 4 seconds later (while the run is still executing server-side), and flicker with each replayed terminal. This looks like exactly what’s described in [langgraphjs#2609](https://github.com/langchain-ai/langgraphjs/issues/2609).
3. **Delivery latency:** the new run’s events queue behind the replay of all prior runs (we’ve measured 10–27 s of lag between server emission and client delivery mid-turn).

## Versions

**Backend (self-hosted):**

| Component | Version |
| --- | --- |
| Docker base image | `langchain/langgraph-api` (dated tag `-20260710`), Python 3.13 |
| `langgraph-api` | 0.10.3 (behaviour also verified unchanged in the 0.12.2 wheel and the 0.14.0.dev3 pre-release) |
| `langgraph` (core) | 1.2.2 (also verified against 1.2.11, latest) |
| `langgraph-checkpoint` / `-postgres` | 4.1.1 / 3.1.0 |
| `langchain-core` | 1.4.0 |
| `langgraph-cli` / `langgraph-runtime-inmem` (local dev) | 0.4.28 / 0.30.2 — all issues reproduce identically on the local dev server |

**Frontend:**

| Component | Version |
| --- | --- |
| `@langchain/react` | 1.0.28 |
| `@langchain/langgraph-sdk` | 1.9.27 (findings re-verified against the 1.9.29 tarball — the relevant modules are unchanged) |
| React | 19 |

## What we’ve noticed in our investigation

We instrumented the wire (every protocol event at `ThreadStream`’s intake, subscription pause/resume, projection decisions). Findings, with code references:

1. **The step-based staleness guard can never engage on Python backends.** `RootMessageProjection.applyValues` treats a snapshot as a stale replay only when it carries a checkpoint `step` lower than the max applied (`addOnly`). Steps are paired from companion `checkpoints`-channel events — but those are **never emitted for Python graphs** : `langgraph` core (through 1.2.11) emits the legacy `CheckpointPayload` shape (`config/metadata/values/next/parent_config`, no top-level `id`), while `langgraph_api.event_streaming.session`’s checkpoints intake requires `isinstance(data.get("id"), str)` and silently drops everything else (verified through `langgraph-api` 0.14.0.dev3). We measured **0 `checkpoints` events out of 583 protocol events**. Net effect: every replayed `values` snapshot is step-less and applied wholesale.
2. **Even when `addOnly` engages, it only protects the `messages` key** — both branches of `applyValues` spread the incoming stale snapshot over the rest of the state (`{ ...nextValues, [messagesKey]: messages }`), so non-message state rewinds regardless.
3. **Replayed terminal lifecycle events affect the current run** (the #2609 class): they settle loading state and each schedules `#scheduleTerminalPause`, which pauses all non-lifecycle subscriptions (we logged 14 pauses / 6 resumes in an 18-second window). `SubscriptionHandle.pause()` resolves pending iterators with `done: true`, so consumer loops exit and delivery becomes dependent on re-iteration after resume.
4. **Transport reconnection gaps:** the `since` cursor is only sent on the first connection attempt (reconnects replay from seq 0 — absorbed by per-subscription `event_id` dedupe, but costly), and a **clean** stream close terminates `openEventStream`’s read loop permanently — reconnection exists only in the error path, which would affect deployed environments behind load balancers/proxies that idle-close connections.
5. **Message ordering:** a turn’s messages arrive out of persisted order (the final message’s stream starts first; narration and tool/artefact messages are inserted _before_ it at persist time). We’ve adapted our client to adopt the persisted order at run completion.

## Our architecture

A single Python LangGraph graph on self-hosted `langgraph-api`; React SPA (React Router, SPA mode). The route loader fetches `threads.getState()` + latest run for hydration, then:

```tsx
const stream = useStream<AgentState>({
  threadId, // from the route
  client, // langgraph-sdk Client (authenticated)
  assistantId,
  initialValues, // loader's getState() values
  onThreadId, onCreated, onCompleted,
});

// stream.values → app state (title, task sections, artefacts, sidebar viewing state)
// stream.messages → transcript
// plus custom channels via the thread handle (chain-of-thought, artefact
// content deltas, analytics …) emitted server-side with stream_mode="custom":
useChannelEffect(stream, ["custom:fd-state", "custom:fd-artefact-content", …], handler, { replay: false });

```

On `onCompleted` we re-fetch `getState()` and treat it as the authoritative snapshot. Non-message app state is driven directly from `stream.values`, which is why replayed snapshots are so visible for us.

## Summary — what we’re really asking

Our core question is not about any single internal detail. It’s:

**What is the intended/supported way to handle page refreshes and SSE reconnection when the protocol replays buffered events?**

Concretely, for an app whose UI is driven by `stream.values` and `stream.messages`:

1. **On a page refresh of an existing thread** (idle, or with a run in flight): what should the client do so that hydration + the subsequent replay converge cleanly — is there a combination of `useStream` options, hydration strategy, or subscribe parameters we should be using that we’ve missed? Are clients expected to treat replayed events as authoritative, or to ignore them in favour of `getState()` — and if the latter, how should a client distinguish replayed events from live ones?
2. **On reconnection** (network blip, proxy/LB closing the stream, or a stream that ends cleanly): what’s the recommended pattern for rejoining the live stream without re-consuming — or being visibly affected by — the full replay, and without losing events emitted while disconnected?

Does any of this involve having to upgrade, or are there common patterns that I am missing to combat these problems we are facing?

Thanks for any help in advance, it is much appreciated.

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [August 12, 2026, 4:03pm UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341/2 "2026-08-12T16:03:36Z")

</div>

Hi @reecemillsom - your investigation is correct on every point. I verified each claim against source (`@langchain/langgraph-sdk` 1.9.29 / `@langchain/react` 1.0.30, `langgraph` 1.2.11, and the `langgraph-api` 0.10.3 + 0.12.3 wheels). **Upgrading won’t fix this today.**

**Root cause - a JS - Python parity gap.** The client’s anti-replay machinery (checkpoint-`step` staleness guard, seeded-message seal, `event_id` dedup) depends on lightweight `{id, parent_id, step, source}` envelopes on the `checkpoints` channel. The JS runtime emits them natively (`_emitValuesWithCheckpointMeta` in `langgraph-core/src/pregel/loop.ts`). Python core still emits the legacy full-state `map_debug_checkpoint` dict, and `langgraph_api.event_streaming.session` drops anything without a top-level string `id` - in 0.10.3 and 0.12.3 alike. So `step` never arrives, `addOnly` never engages, and replayed `values` snapshots rewind the UI. Your “0 of 583” is exactly what the code predicts. Your other findings are real too: `addOnly` only protects `messages`; `LifecycleLoadingTracker` filters stale `running` but not stale terminals; clean stream close permanently ends the read loop (reconnect exists only in the error path).

**Q1 (refresh):** the intended model is `getState()` = authoritative, replay converges via the step guard - which Python backends starve. Until fixed, replicate the guard with data you control:

1. Add a monotonic `rev` to your graph state (`Annotated[int, max]`), bumped by nodes that touch UI state.
2. Don’t bind UI to `stream.values` directly - project through an apply-if-newer layer seeded from your loader’s `getState().values.rev`; skip snapshots with lower `rev`.
3. Derive the thinking indicator from your own submit lifecycle (pending flag on `submit()`, cleared by first new token or newer `rev`), not `stream.isLoading`.

**Q2 (reconnect):** errored connections are handled (reconnect + full replay + `event_id` dedup; missed events recovered while the 10k ring buffer holds them - watch `lg_api_protocol_v2_resume_gap_counter`). For proxy idle-kills, tune `streamIdleReconnect`. Clean close is a genuine gap - the supported recovery is remounting the hook with the saved `threadId` ([Join & rejoin](https://docs.langchain.com/oss/javascript/langchain/frontend/join-rejoin#rejoining-a-stream)).

**Optional server stopgap:** since the session forwards any `checkpoints` payload with a string `id`, you can patch `langgraph.pregel._loop.map_debug_checkpoint` to yield `{"id": config["configurable"]["checkpoint_id"], "step": metadata["step"], "source": metadata["source"], "parent_id": ...}` instead of the full-state dict. Caveats: private API, changes classic `stream_mode=["checkpoints"]`/`"debug"` output for other consumers, and it fixes staleness detection but not the non-message `addOnly` spread - keep the `rev` guard regardless.

**Please file these** (no existing issues cover them; your instrumentation makes a strong report): (1) Python envelope emission / `langgraph-api` synthesis shim → [langchain-ai/langgraph](https://github.com/langchain-ai/langgraph/issues); (2) `addOnly` non-message spread, (3) terminal-replay loading flicker, (4) clean-close reconnect → [langchain-ai/langgraphjs](https://github.com/langchain-ai/langgraphjs/issues).

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [August 12, 2026, 4:06pm UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341/3 "2026-08-12T16:06:51Z")

</div>

imo three usable fixes today (they’re independent):

**1. Client-side `rev` guard (recommended, safe).** Replicates the SDK’s starved staleness guard with data you control. Graph side:

```python
def _max_rev(a, b): return max(a or 0, b or 0)

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]
    rev: Annotated[int, _max_rev]

# in any node that mutates UI-relevant state:
return {"title": new_title, "rev": state.get("rev", 0) + 1}

```

Client side - stop binding UI to `stream.values` directly; project apply-if-newer, seeded from your route loader’s `getState()`:

```tsx
const lastRev = useRef(loaderState?.values?.rev ?? -1);
const [uiState, setUiState] = useState(loaderState?.values);

useEffect(() => {
  const v = stream.values;
  if (v?.rev != null && v.rev >= lastRev.current) {
    lastRev.current = v.rev;
    setUiState(v);
  }
}, [stream.values]);

```

Replayed snapshots carry older `rev` and get skipped - this kills the visible rewind regardless of what upstream does. Same idea for the thinking indicator: local `pending` flag set on `submit()`, cleared by the first new token or a newer `rev`, instead of `stream.isLoading`.

**2. Server-side monkey-patch (riskier, fixes the mechanism itself).** The session forwards any `checkpoints` payload with a top-level string `id`, so patch the mapper before your graph loads:

```python
from langgraph.pregel import _loop

def _map_checkpoint_envelope(config, channels, stream_channels, metadata,
                             tasks, pending_writes, parent_config, output_keys):
    envelope = {
        "id": config["configurable"]["checkpoint_id"],
        "step": metadata["step"],
        "source": metadata["source"],
    }
    parent_id = (parent_config or {}).get("configurable", {}).get("checkpoint_id")
    if parent_id:
        envelope["parent_id"] = parent_id
    yield envelope

_loop.map_debug_checkpoint = _map_checkpoint_envelope

```

This makes the SDK’s `addOnly` guard and seal-lifting actually engage (verify with your wire instrumentation: you should see `checkpoints` events pairing each root `values` event). Caveats: private API (re-check on every `langgraph` upgrade), changes classic `stream_mode=["checkpoints"]`/`"debug"` output for other consumers (e.g. Studio debug views), and it does **not** fix the non-message `addOnly` spread - so keep workaround 1 anyway. Source-verified but I haven’t run it end-to-end.

**3. For the clean-close disconnect:** remount the hook with the saved `threadId` (bump a `mountKey`) when the stream ends - the documented [join/rejoin pattern](https://docs.langchain.com/oss/javascript/langchain/frontend/join-rejoin#rejoining-a-stream) - and keep `streamIdleReconnect` on for proxy idle-kills.

Nothing helps the 10–27 s replay latency on long threads except shorter threads or a smaller server buffer (`LSD_PROTOCOL_V2_BUFFER_SIZE`, at the cost of resume window) - that queue-behind-replay behavior is by design until the upstream fixes land.

---

<div class="post-metadata">

**Author:** ![christian-bromann](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/christian-bromann/32/914_2.png) [@christian-bromann](https://forum.langchain.com/u/christian-bromann)\
**Post date:** [August 12, 2026, 4:17pm UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341/4 "2026-08-12T16:17:20Z")

</div>

Is this related to [Protocol v2: stale lifecycle replay settles first submit after thread hydration · Issue #2609 · langchain-ai/langgraphjs · GitHub](https://github.com/langchain-ai/langgraphjs/issues/2609)?

---

<div class="post-metadata">

**Author:** ![reecemillsom](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/reecemillsom/32/963_2.png) [@reecemillsom](https://forum.langchain.com/u/reecemillsom)\
**Post date:** [August 17, 2026, 7:52am UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341/5 "2026-08-17T07:52:16Z")

</div>

Hi @pawel-twardziak ,

So sorry for the slow reply, I will take a look into the options that you have suggested and will reply hopefully sooner rather than later.

Really appreciate your responses 🙂.

---

<div class="post-metadata">

**Author:** ![pawel-twardziak](https://yyz1.discourse-cdn.com/flex007/user_avatar/forum.langchain.com/pawel-twardziak/32/960_2.png) [@pawel-twardziak](https://forum.langchain.com/u/pawel-twardziak)\
**Post date:** [August 17, 2026, 8:39am UTC](https://forum.langchain.com/t/refreshing-event-stream-using-langchain-react-use-stream-replays-old-checkpoints/4341/6 "2026-08-17T08:39:31Z")

</div>

hi @reecemillsom

that’s totally fine 🙂  
And, look at the issue that @christian-bromann has mentioned above.
