Skip to content

feat(server,web): reliably retract sent turns and restore editing - #11

Merged
asfires merged 22 commits into
mainfrom
t3code/restore-sent-message-editing
Aug 12, 2026
Merged

asfires merged 22 commits into
mainfrom
t3code/restore-sent-message-editing

Conversation

@asfires

@asfires asfires commented Aug 12, 2026

Copy link
Copy Markdown
Owner

What Changed

  • Added durable turn-retraction orchestration with provider rollback, checkpoint baselines, completion tracking, and recovery handling.
  • Updated Codex and Claude adapters to interrupt concrete turns, roll back conversation context, serialize sends across session recycling, and retry early interrupts safely.
  • Added optimistic web retraction, Esc-based dispatch, message restoration, draft handoff, and silent reconciliation.
  • Added migrations, capability wiring, repro tooling, and extensive server, client-runtime, and web coverage for retries, races, session recycling, and cross-version behavior.

Why

Previously, retracting a just-sent message could leave provider context, turn state, or the client out of sync—especially during early interrupts, session recycling, or rapid retract-and-resend flows. This change makes retraction durable and convergent: the concrete provider turn is rolled back to the correct checkpoint, the UI responds immediately, and restored messages can be edited and resent without wedging the thread or leaving stale model context behind.

UI Changes

The chat UI now optimistically removes a just-sent user message on Esc, restores it into the composer when retraction completes, and reconciles silently with durable server state. The change also updates related keybinding and message timeline behavior. Before/after screenshots and interaction video should be added for review.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

asfires added 22 commits August 11, 2026 11:31
…stant output

Pressing Esc between send and first assistant output interrupts the turn,
reverts to the pre-turn checkpoint, and restores the message into the
composer. Claude provider rollback was previously cosmetic: resumeSessionAt
was never passed to the SDK and live sessions kept rolled-back history.
Rollback now maintains per-turn assistant-uuid watermarks, clears the resume
cursor at turn zero, and recycles the session before the next turn.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…action intent

Adds the client-dispatchable retract command, threadTurnRetraction capability,
additive retraction metadata on turn-interrupt-requested/reverted payloads,
a durable pending-retraction projection (migration 041) with startup scan,
and authoritative decider eligibility incl. the no-assistant-output rule.
Nothing consumes the intent yet; the retraction process manager follows.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…d gating

Retract can now cancel an unclaimed provider send before session spawn, and
sends are atomically claimed after the pre-turn baseline checkpoint exists.
Adds the dispatch-ownership ledger (migration 042) and restart classification.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…ommand

Bootstrap-created worktrees record provenance; thread.turn.retract.complete
atomically emits reverted (+deleted for first-message retractions), and
deletion cleanup removes only T3-owned, unshared worktrees.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…ry draft handoff

Esc now works with focus anywhere in the chat view (bubble phase; menus win),
cancels sends locally before the start RPC dispatches, and capability-gated
machinery hands first-message retractions back to a fresh seeded draft.
Empty persisted threads render the project hero instead of the bare placeholder.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
Renumbered the managed-worktrees migration to 043 to resolve the parallel
042 collision with the dispatch-ownership ledger.
…rovider rollback

TurnRetractionReactor drives requested retractions to completion or terminal
failure from projected state, lifecycle events, and a startup scan. Adds
rollbackConversationTo (verified absolute boundary) for Claude and Codex,
late-event tombstone suppression, and new-turn blocking while pending.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
Retraction is now one correlated command with persistent commandId; the
interrupt/settle/revert saga and checkpoint heuristics are deleted (ChatView
net -112 LOC). First-message completions navigate to a seeded fresh draft;
mid-thread completions restore into the thread composer; failures always
preserve the user's text.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
… pending scan

Adds a 30s pending-row re-enqueue so retryable retraction failures converge
without lifecycle events, capability gating on the web dispatch seam, and
coverage for commandId replay, duplicate completion, additive-metadata
tolerance, and pre-retraction snapshot decode.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
The handoff watched a transient completion stream; cancelled-path
completions land in milliseconds and beat the subscription, stranding the
user on a deleted thread. The watcher now lives in the persistent chat
layout and fires on correlated completion, thread deletion, shell absence,
correlated failure, or a 60s staleness guard.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…ctor

Object.values inside the zustand selector returned a fresh array every
snapshot, looping useSyncExternalStore from the persistent chat layout and
crashing every route. Select the stable map and derive values outside.

Built with Claude Fable 5 via Claude Code.
…lback arithmetic, correct client retraction correlation

The retraction worker blocked forever awaiting a redundant turn/interrupt
against a settled turn; interrupts are now once-per-request, bounded, and
every convergence evaluation logs its outcome. Claude absolute rollback now
offsets session-local turns by the resumed cursor's lifetime count. The
client reducer maintains turnRetraction incrementally so pending rows
suppress the staleness fallback, and failure/stale recovery never navigates.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…h providers

Claude's absolute rollback skipped cursor rewind and query recycle at zero
delta, so the live SDK session kept interrupted prompts. Codex count
arithmetic saw retained==reported and rolled back nothing while the
interrupted turn remained live. Both paths now share finalization (Claude)
or roll back from the concrete target turn with absence and retained-prefix
verification (Codex). The wire repro gates on post-retract interrogation.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…cting

An interrupt issued while the session was still starting consumed the
request's once-only budget, so long Claude turns ran to natural completion
before rollback. The reactor now waits for the concrete turn and interrupts
per turn id; the repro gains long-response scenarios asserting retraction
beats natural completion on both providers.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
…cadence

Interrupts landing before the SDK begins processing were silently swallowed;
the adapter now replays a latched interrupt on system/status=requesting, and
the reactor re-issues every 2s while the target turn stays projected-running.
Double-pop gate: 8/8 Claude retractions <= 723ms (was up to 18.9s).

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
The composer restores and the message row hides synchronously at keypress;
completion reconciles without double-merges, failures un-hide and toast,
first-message recovery navigates on command acceptance, and sends during a
pending retraction hold up to 20s instead of erroring.

Built with Claude Fable 5 via Claude Code, implementation by Codex (GPT-5.6-Sol).
While a client-known pending retraction exists, one effective-presentation
derivation keeps the timeline working row, the composer stop button and the
sidebar Working badge quiet, so the popped turn reads as never started.
Suppression drops on terminal failure or when a different turn goes live
(a held send dispatching), and shares the pending-recovery lifetime so a
stuck retraction cannot masquerade as idle.

Built with Claude Fable 5 via Claude Code, implementation by Claude Opus 5.
The post-rollback recycle replaces the SDK session over several hundred ms
during which the thread looks idle, so a second send could race the recycle:
both called startSession, and the losing session kept an already-opened,
prompt-offered turn no live query could settle, pinning the session
"running" forever. A per-thread semaphore makes recycle + turn-open +
prompt-offer atomic. Adds interrupt-replay arm/fire logging and a wire-level
pop-resend-follow repro scenario (fails pre-fix, passes N=3 post-fix).

Built with Claude Fable 5 via Claude Code, implementation by Claude Opus 5.
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Aug 12, 2026
@asfires
asfires merged commit 729dcd7 into main Aug 12, 2026
6 of 11 checks passed
@asfires
asfires deleted the t3code/restore-sent-message-editing branch August 12, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant