Fix chat history reliability: use wisplog as authoritative source#133
Open
mcintyre94 wants to merge 3 commits into
Open
Fix chat history reliability: use wisplog as authoritative source#133mcintyre94 wants to merge 3 commits into
mcintyre94 wants to merge 3 commits into
Conversation
After the phone goes to sleep mid-stream and reconnects, SwiftData can end up persisting a partial message history that starts with an assistant message (e.g. the first user+assistant exchange is lost). This happens in an edge case of the reconnect flow where persistMessages fires before the full context is in memory. Extend the empty-messages check in reconnectIfNeeded to also trigger a wisplog reload when messages.first?.role == .assistant — chats must always start with a user message, so an orphaned leading assistant message is a reliable signal of truncated history. The wisplog on the sprite always has the full conversation. Adds a regression test covering this case. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Three related fixes for chat history reliability: 1. Reload from wisplog after every completed stream. Both executeClaudeCommand and reattachToExec now fire a background loadFromWispLog when a session finishes cleanly with no queued follow-up. This corrects any history truncation (including user-first truncation, not just the assistant-first case caught by the earlier reconnectIfNeeded guard) without requiring heuristics to detect which messages were lost. A guard in loadFromWispLog bails out if the user sends another message before the cat completes. 2. Reload on cold open of completed chats. The lastSessionComplete early-return in reconnectIfNeeded now distinguishes the undelivered-draft edge case (messages.last == .user → restoreUndeliveredDraft as before) from the normal case (messages.last == .assistant → background wisplog verify). This closes the gap where a cold open could show user-first truncated history indefinitely. 3. Check wisplog before the slow-response retry. When executeClaudeCommand times out with no data, it now reads the wisplog first. If Claude finished before the WebSocket connected (fast exec), the response is loaded from the log and the retry is skipped — avoiding the duplicate user prompt that was previously written to the wisplog. Also adds a regression test for the thinking-block + text-block pattern that caused the missed response in the fast-exec case. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
Previously loadFromWispLog only called persistMessages, leaving lastSessionComplete=false and execSessionId set in SwiftData. This caused an unnecessary reattachToExec attempt on the next reconnectIfNeeded call, which would time out, call restoreFromSessionFile, and read the wisplog a second time before finally marking the session complete. Apply the same pattern as restoreFromSessionFile: if the loaded messages end with a user message, surface it as a draft (undelivered prompt edge case); otherwise clear execSessionId and saveSession(isComplete:true) so the next reconnectIfNeeded takes the fast completed-session path. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
reconnectIfNeededcould display a chat starting with an assistant message (user messages lost). The existingmessages.first?.role == .assistantguard catches assistant-first truncation; the new post-stream wisplog reload catches all other truncation patterns including user-first.processExecStreamsees no data and fires the retry path — writing a duplicate user prompt to the wisplog and discarding the actual response. Now checks the wisplog first and loads the response directly if it's there.lastSessionCompleteearly-return trusted SwiftData blindly. Now triggers a background wisplog verify for normally-completed sessions, so user-first truncated history is corrected on the next chat open rather than waiting for the next message send.How it works
After every completed streaming session (
executeClaudeCommandandreattachToExec), a backgroundloadFromWispLogfires. The wisplog is the canonical record of the conversation; this makes SwiftData always converge to it. A guard inloadFromWispLogaborts if the user sends another message before thecatcompletes, preventing race conditions.Test plan
🤖 Generated with Claude Code