Skip to content

Fix chat history reliability: use wisplog as authoritative source#133

Open
mcintyre94 wants to merge 3 commits into
mainfrom
chat-logs-wisp-issue-5df77482
Open

Fix chat history reliability: use wisplog as authoritative source#133
mcintyre94 wants to merge 3 commits into
mainfrom
chat-logs-wisp-issue-5df77482

Conversation

@mcintyre94

Copy link
Copy Markdown
Owner

Summary

  • Truncated history on reconnect: After the phone sleeps mid-stream, reconnectIfNeeded could display a chat starting with an assistant message (user messages lost). The existing messages.first?.role == .assistant guard catches assistant-first truncation; the new post-stream wisplog reload catches all other truncation patterns including user-first.
  • Missed response on fast exec: When Claude finishes before the WebSocket connects, processExecStream sees 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.
  • Cold-open gap: The lastSessionComplete early-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 (executeClaudeCommand and reattachToExec), a background loadFromWispLog fires. The wisplog is the canonical record of the conversation; this makes SwiftData always converge to it. A guard in loadFromWispLog aborts if the user sends another message before the cat completes, preventing race conditions.

Test plan

  • Send a message, let it complete — history unchanged (wisplog reload is a no-op)
  • Sleep phone mid-stream, wake and reconnect — full history restored
  • Sleep phone mid-stream, Claude finishes while asleep — full history restored without duplicate user prompt in wisplog
  • Open a chat with no recent activity — wisplog verify runs in background, no visible delay
  • Send a message immediately after opening — wisplog reload aborts cleanly, new message is not lost

🤖 Generated with Claude Code

mcintyre94 and others added 2 commits May 1, 2026 16:07
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>
@claude

claude Bot commented May 1, 2026

Copy link
Copy Markdown

Code review

No 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant