[Fix] Live transcript converges to persisted envelopes on every history refetch - #37
Merged
Merged
Conversation
…ry refetch The live task page reconstructed history through two different code paths: initial load used the envelope loader (loadAcpEnvelopes, the same builder the always-correct historical view uses), but every subsequent refetch patched the in-memory list envelope-by-envelope through the live-event handler, skipping any envelope whose id was already present. Once the live state drifted - missed socket events, replacements applied at stale positions, ordering skew - the drift persisted until the sandbox slept, matching the corruption observed on staging (transcripts collapsing to the prompt plus latest answer, blank renders, steered messages out of order). _mergeAcpHistory now rebuilds the transcript from the fetched envelopes with the same loader, then carries over only messages the fetch does not cover yet (optimistic sends and unpersisted live events, matched by logical event id, clientMessageId, tool call id, or originating event timestamp). Every refetch is a convergence point to server truth, and an identical refetch keeps the previous references so nothing re-renders. The new steered-task regression suite includes a drift-healing case that fails against the previous incremental merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2 issues outstanding. See task
|
daniel-lxs
added a commit
that referenced
this pull request
Jul 9, 2026
…ion panel fixes Three issues from the openmote reviews of #33 and #37: - The history rebuild no longer resets the protocol service's auxiliary lookups: rebindMessages re-points only the id/position indexes, so the pending request_user_input map populated from history survives a refetch and a subsequent live response still masks secret answers. - Todos now converge to the fetched history whenever it carries plan state, including an intentionally empty list, instead of falling back to stale live todos. - A taskAborted status (cancel or terminal session error) drops the client-side pending user input requests, matching the worker-side clear, so an aborted question can no longer stay answerable and swallow free-text sends. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
daniel-lxs
added a commit
that referenced
this pull request
Jul 9, 2026
…ion panel fixes (#44) * [Fix] Address review findings on the transcript convergence and question panel fixes Three issues from the openmote reviews of #33 and #37: - The history rebuild no longer resets the protocol service's auxiliary lookups: rebindMessages re-points only the id/position indexes, so the pending request_user_input map populated from history survives a refetch and a subsequent live response still masks secret answers. - Todos now converge to the fetched history whenever it carries plan state, including an intentionally empty list, instead of falling back to stale live todos. - A taskAborted status (cancel or terminal session error) drops the client-side pending user input requests, matching the worker-side clear, so an aborted question can no longer stay answerable and swallow free-text sends. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [Fix] Address second-round review findings on the merge reconciliation - The history rebuild now derives the trailing turn-completion decision from the live task status (falling back to what the transcript already shows), instead of always marking it - merging while a task is running no longer flips the active trailing assistant message to completed. - todowrite plan state arriving through ToolCall/ToolCallUpdate history envelopes now counts as authoritative plan history for todo convergence. - Live terminal tool results are matched by terminality rather than id/tool key: a persisted pending tool_call no longer swallows the live result (which can sit under the same envelope id after an in-place update), and the superseded pending call row is dropped instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [Fix] Match live tool results against persisted terminality, not result kind A persisted in-progress ToolCallUpdate also rebuilds as a (partial) tool_result, so kind alone let it swallow a live terminal result and leave the transcript stuck on stale in-progress output. Coverage now requires a non-partial persisted result for live terminal results, and carried terminal results supersede both pending tool_call and partial tool_result rebuilt rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <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.
What problem this solves
Root-cause fix for the live-session transcript corruption from the steering test pass (tasks
190holbb4xm2k,11nl0xsy05oylon openmote): transcripts collapsing to the initial prompt + latest answer after a reload, blank renders mid-session, steered messages rendering out of chronological order, and missed live replies staying missing — always self-resolving once the sandbox slept (historical rendering was always correct, and server envelope data was always intact).Root cause
The live page reconstructed history through two different code paths:
loadAcpEnvelopes— the envelope-aware builder the historical view uses (always correct)._mergeAcpHistorypatched the in-memory list envelope-by-envelope through the live-event handler, and skipped any envelope whose id was already present.The skip-by-id rule means any drift in the live in-memory state (missed socket events, replacements at stale indexes, ordering skew) could never be corrected by a refetch — it persisted until the sandbox slept and the page fell back to the historical loader.
What changed
_mergeAcpHistorynow rebuilds the transcript from the fetched envelopes with the sameloadAcpEnvelopesbuilder, then carries over only messages the fetch doesn't cover yet: optimistic sends and live-only events, matched via logical event id, userclientMessageId,toolCallId, or the originating event timestamp. Every refetch — which fires on every socket (re)connect — is now a convergence point to the server's authoritative transcript.Provability
The new
steered-task-history.client.test.tssuite (7 tests) includes a drift-healing case — content corrupted under a matching envelope id — that fails against the previous merge implementation (verified: 1 failed / 6 passed on the old code) and passes with the rebuild. Also covered: collapse-recovery to full transcript, carried-over optimistic + unpersisted live messages, timestamp ordering for envelopes the socket missed, and the no-change fast path.Validation
Full
src/app/(sandbox)/task/suite green (483 tests, incl. the 64-case store suite),pnpm lint:fast+check-types:fastclean.🤖 Generated with Claude Code