Skip to content

fix(chat): don't wipe fresh streaming/tool-timeline on a completed hydrate snapshot - #4903

Merged
graycyrus merged 1 commit into
tinyhumansai:mainfrom
graycyrus:fix/flows-hydrate-streaming-flicker
Jul 15, 2026
Merged

graycyrus merged 1 commit into
tinyhumansai:mainfrom
graycyrus:fix/flows-hydrate-streaming-flicker

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Bug

hydrateRuntimeFromSnapshot in app/src/store/chatRuntimeSlice.ts was fixed in #4886 to only clear pendingWorkflowProposalsByThread on snapshot.lifecycle === 'interrupted' (not 'completed') — a completed snapshot can be this session's own just-settled turn racing against the live path.

The same reducer still unconditionally cleared streamingAssistantByThread[threadId] and rewrote toolTimelineByThread[threadId] for any terminal snapshot, including completed. Concretely, ChatRuntimeProvider's socket-disconnect reconciliation deliberately keeps streamingAssistantByThread set across endInferenceTurn so a partial reply stays visible while the socket reconnects (see the comment at ChatRuntimeProvider.tsx:1403). If a completed fetchAndHydrateTurnState rehydration then lands for that thread, it wiped the just-preserved streaming bubble and replaced the live (richer) tool timeline with the (behind) persisted snapshot — a transient flicker / loss of just-streamed narration, even though the proposal card itself now survives.

Fix

Split the terminal-snapshot block in hydrateRuntimeFromSnapshot:

  • inferenceStatusByThread clear and processingByThread assignment still happen unconditionally for both interrupted/completed (unaffected by this bug).
  • The streamingAssistantByThread delete + toolTimelineByThread overwrite now only run when snapshot.lifecycle === 'interrupted' (genuine crash — nothing fresher can exist) or there is no live streaming state for the thread (!state.streamingAssistantByThread[threadId]), which covers cold boot / new window so history still hydrates.

This mirrors the interrupted-only invariant already used for the workflow-proposal clear from #4886, keeping the three guards (proposal / streaming lane / tool timeline) consistent.

Tests

Added to app/src/store/chatRuntimeSlice.test.ts (hydrateRuntimeFromSnapshot — streaming/timeline race guard):

  • does not wipe a fresher streaming/tool-timeline lane on a completed snapshot
  • clears the streaming/tool-timeline lanes on an interrupted snapshot (crash cleanup)
  • still hydrates the timeline from a completed snapshot on cold boot (no prior live state)

Test plan

  • corepack pnpm typecheck clean
  • corepack pnpm lint clean (0 errors; pre-existing unrelated warnings in other files)
  • corepack pnpm test --config app/test/vitest.config.ts src/store/chatRuntimeSlice.test.ts — 30/30 passed
  • corepack pnpm format — no diff beyond the intended change

Summary by CodeRabbit

  • Bug Fixes

    • Improved chat state recovery after reconnection to prevent active streaming responses and tool activity from being overwritten by older completed snapshots.
    • Ensured interrupted conversations are properly cleared and cleaned up.
    • Preserved tool activity history when restoring completed conversations during a cold start.
  • Tests

    • Added regression coverage for reconnect races, interrupted sessions, and cold-start restoration.

…drate snapshot

hydrateRuntimeFromSnapshot unconditionally cleared streamingAssistantByThread
and rewrote toolTimelineByThread for any terminal snapshot (interrupted OR
completed). A completed snapshot can lag behind live state this session
already has for the thread — e.g. the socket-disconnect reconciliation path
deliberately keeps streamingAssistantByThread set across endInferenceTurn so
a partial reply stays visible while the socket reconnects, and a late
fetchAndHydrateTurnState rehydration can then land and wipe it.

Only let the snapshot clobber those two lanes when it's unambiguously the
authority: an interrupted snapshot (genuine crash, nothing fresher can
exist) or there's no live streaming state to lose (cold boot / new window).
Mirrors the interrupted-only guard already applied to the workflow-proposal
clear in tinyhumansai#4886.
@graycyrus
graycyrus requested a review from a team July 15, 2026 13:16
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

hydrateRuntimeFromSnapshot now avoids overwriting fresher live streaming and tool-timeline state for completed snapshots, while interrupted snapshots still clear those lanes. Tests cover reconnect races, crash cleanup, and cold-boot hydration.

Changes

Runtime snapshot hydration

Layer / File(s) Summary
Guard terminal snapshot reconciliation
app/src/store/chatRuntimeSlice.ts
Completed snapshots preserve existing live streaming and tool-timeline state; interrupted snapshots and snapshots without live streaming retain cleanup and settlement behavior.
Validate hydration race behavior
app/src/store/chatRuntimeSlice.test.ts
Tests verify live-state preservation, interrupted cleanup, and cold-boot tool-timeline hydration.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: bug

Suggested reviewers: oxoxdev, senamakel, sanil-23

Poem

I’m a bunny guarding streams in flight,
Keeping fresh live lanes safe and bright.
Completed snapshots wait their turn,
Interrupted paths clean what they burn.
Cold boots bloom with timelines true—
Hop, hop, the race guard’s through!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: preserving fresh streaming and tool-timeline state during completed snapshot hydration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/src/store/chatRuntimeSlice.ts (1)

1993-2014: 📐 Maintainability & Code Quality | 🔵 Trivial

Add a branch-level chatRuntime.turnState log in this completed/interrupted guard.

fetchAndHydrateTurnState already logs the hydrate attempt, but this reconciliation still has no signal for whether the live stream was preserved or cleared. Log only lifecycle and hasFresherLiveStream so reconnect races stay grep-friendly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/store/chatRuntimeSlice.ts` around lines 1993 - 2014, Add a
branch-level chatRuntime.turnState log within the snapshot lifecycle guard
around hasFresherLiveStream, recording only snapshot.lifecycle and
hasFresherLiveStream. Emit it whether the live stream is preserved or cleared,
without logging snapshot contents or other state.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/store/chatRuntimeSlice.ts`:
- Around line 1993-2014: Add a branch-level chatRuntime.turnState log within the
snapshot lifecycle guard around hasFresherLiveStream, recording only
snapshot.lifecycle and hasFresherLiveStream. Emit it whether the live stream is
preserved or cleared, without logging snapshot contents or other state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 932ec7bc-4ebd-4e00-a0b6-747a6eb0a0b7

📥 Commits

Reviewing files that changed from the base of the PR and between 0e86b85 and 049d3e1.

📒 Files selected for processing (2)
  • app/src/store/chatRuntimeSlice.test.ts
  • app/src/store/chatRuntimeSlice.ts

@graycyrus
graycyrus merged commit 095e3c3 into tinyhumansai:main Jul 15, 2026
22 of 26 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 15, 2026
senamakel pushed a commit to M3gA-Mind/openhuman that referenced this pull request Jul 18, 2026
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant