Repository navigation
Rewind Claude chats by forking the kept conversation - #30400
brennanb2025 wants to merge 25 commits into
Conversation
Rewind copied the kept part of the chat into its saved record and, for Codex, re-read and re-translated the whole provider history before and after the revert. Both were bounded (10,000 rows / 2 MB host, 1,024 entries / 2 MB Codex), so long chats were refused with "too long to go back in". The record now names the cut; the kept rows are derived from the journal, which keeps the old epoch until the rewind completes. Codex reads only the turns it undoes and trusts thread/revert; recovery proves an applied revert from the newest turn alone and never truncates otherwise.
…nversation A rewind record can name the provider session the chat continues in (providerFork). Claude's next launch forks it with the SDK's own truncating resume at the rewound prompt's parent, and publishes a forked link once the provider has created it. Removes the transcript proof branch that could never pass on the real CLI.
Claude declares rewind. The adapter answers where a rewind forks (the rewound prompt's parent in its own transcript); the host saves that fork with the prepared record, stops the agent, and rebuilds the journal from the rows before the message. Recovery completes a prepared fork, since nothing outside Orca can have half-happened, and the next launch makes the fork.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🟡 Moderate · up to
|
… into brennanb2025/chat-rewind-availability-repro
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
e7736a6d-127c-43f1-8995-ba6650ec0498
📒 Files selected for processing: 42
src/main/ai-vault/structured-chat-cli-fork.tssrc/main/claude/claude-rewind-fork.tssrc/main/claude/claude-structured-agent-definition.tssrc/main/claude/claude-structured-launch-resolution.tssrc/main/claude/claude-structured-owner-identity.tssrc/main/claude/claude-structured-real-cli-rewind.test.tssrc/main/claude/claude-structured-session-acquisition.tssrc/main/claude/claude-structured-session-adapter.tssrc/main/claude/claude-structured-session-id.tssrc/main/claude/claude-structured-session-publication.tssrc/main/claude/claude-transcript-branch-graph.tssrc/main/claude/claude-transcript-rewind-proof.test.tssrc/main/codex/codex-structured-rewind.test.tssrc/main/codex/codex-structured-rewind.tssrc/main/native-chat/agent-session-journal/journal-item-provenance.spec.tssrc/main/native-chat/agent-session-wire/structured-agent-registry.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adapter-router-registry.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adapter-router-test-support.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.tssrc/main/native-chat/agent-session-wire/structured-agent-session-adapter.tssrc/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.tssrc/main/native-chat/agent-session-wire/structured-agent-session-clear-rewind.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-fork-rewind.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-provider-started.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind-at-rest.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind-live-send.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind-provenance.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind-replay.test-fixture.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind-stop-note.test-fixture.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind.tssrc/main/native-chat/agent-session-wire/structured-agent-session-send-preparation.tssrc/main/native-chat/agent-session-wire/structured-rewind-cut.test.tssrc/main/native-chat/agent-session-wire/structured-rewind-cut.tssrc/main/native-chat/agent-session-wire/structured-rewind-journal-body.test.tssrc/main/native-chat/agent-session-wire/structured-rewind-journal-body.tssrc/main/native-chat/agent-session-wire/structured-rewind-recovery.tssrc/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.test.tssrc/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.tssrc/main/runtime/agent-session-rewind-completion.tssrc/main/runtime/structured-claude-pending-rewind.test.tssrc/shared/agent-session-rewind.ts
💤 Files with no reviewable changes: 7
- src/main/claude/claude-transcript-rewind-proof.test.ts
- src/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.test.ts
- src/main/native-chat/agent-session-journal/journal-item-provenance.spec.ts
- src/main/native-chat/agent-session-wire/structured-rewind-journal-body.test.ts
- src/main/native-chat/agent-session-wire/structured-agent-session-provider-started.ts
- src/main/native-chat/agent-session-wire/structured-rewind-journal-body.ts
- src/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.
…e's test double its fork
… into brennanb2025/chat-rewind-availability-repro
|
Claude chats previously had no working Rewind action. This change returns the selected opening prompt to the composer and continues Claude with only the kept conversation. Messages sent during an existing reply are not offered as rewind targets; rewinding its opening prompt removes the whole turn. Final verdict: the review loop is finished on
For Brennan's review: full screenshot sets are in the PR description. Main's missing action (this baseline image was taken without Claude signed in): |
… into brennanb2025/chat-rewind-availability-repro
…ind-availability-repro # Conflicts: # src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts # src/main/native-chat/agent-session-wire/structured-agent-session-rewind.ts # src/main/native-chat/agent-session-wire/structured-rewind-cut.ts # src/main/native-chat/agent-session-wire/structured-rewind-recovery.ts
…of that turn A message sent while Claude is answering joins the running turn. Rewinding to it forks Claude's conversation at that message, so the chat now drops the rest of that turn too instead of keeping replies Claude no longer has. A rewound chat whose kept conversation lives only in another Claude account now gets the same "history is in another account" refusal as a resume, instead of Claude's raw start failure on every launch.
A message sent while the agent is still answering joins that turn. Claude's CLI holds it until a break and records it after the reply; Orca's chat records it when it was sent. Rewinding to it therefore cut the chat and Claude's conversation at different places: the previous fix dropped reply text Claude still remembered, and the version before kept the message itself. For Codex the cut started at the turn's own first message, removing a message the user did not pick. Rewind now returns only to messages that opened their own turn, the common pattern: the chat stops offering it on a message sent mid-reply, and the host refuses one, so a mid-reply message and its turn are always kept or dropped together. The Claude cut goes back to keeping a kept turn's late rows.
…ind-availability-repro # Conflicts: # config/scripts/vitest-sqlite-runtime-files.mjs # src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router-registry.test.ts # src/main/native-chat/agent-session-wire/structured-agent-session-registered-definition.test.ts
…ed rewinds still complete A message opened its turn when the turn record names it, not only when it sits at thread level: restored history scopes its openers to their turns, so those chats lost Rewind. The opener check now runs only when a new rewind is admitted. Recovery cuts wherever an already saved rewind points, so one saved by an earlier build still completes instead of blocking the chat.
…ind-availability-repro
…ind-availability-repro







ELI5
Rewind ("Rewind to here" on one of your messages) now works in Claude chats, the same way it already works in Codex chats. Before this, Claude chats didn't offer it at all.
What Changed
Before: a Claude chat in the chat UI had no Rewind action on its messages. Claude rewind was turned off in September (#22395). The September version checked Claude's saved conversation file for a marker after every rewind, and that check could never pass on the real Claude CLI.
After: Claude messages offer "Rewind to here", with the same confirm dialog as Codex. Picking a message removes it and everything after it, puts its text back in the composer, and Claude no longer remembers the removed turns.
How it works. A Claude rewind makes a copy (a fork) of the conversation that ends just before the message you picked. The chat then continues in that copy. There is no check afterwards: Orca trusts Claude's own cut, which is the common pattern.
Finding the cut. When you click Rewind, Claude's adapter looks up the message in Claude's own conversation file and takes the entry just before it. That entry is the last entry of the turn you keep, which is the fork point Claude's SDK documents. If the message is the chat's first, the chat starts a fresh session instead.
Saving the rewind. The rewind record gets one new optional field,
providerFork, saved before anything else happens. It holds the new session id, the session being copied, the cut point, and the session the chat was on at the time. The host stops the running Claude process (a quiet stop, not shown as "Interrupted") and rebuilds the chat's history from Orca's own saved rows before the message. Claude is not called during the rewind.Making the fork. The next Claude launch sees the saved fork. It starts the new session with the SDK's own truncating resume (
resume+resumeSessionAt+forkSession+ a fixedsessionId). The chat records the new session as "forked" once Claude has actually created it, the same way Pi records its forks. Once Claude has written the new session's file, later launches resume it normally.Crash safety. A rewind interrupted after its record was saved always completes, because there is no half-finished Claude call to doubt. Until then, the next launch already forks, so it never resumes the conversation that was rewound. A pending Claude rewind left by a build from before September 23 has no saved fork; it is still settled as refused.
Open in terminal is refused, with a plain message, between a rewind and your next message. The interactive Claude CLI ignores the cut option, so opening the chat in a terminal then would bring the removed turns back.
Rewind only to a message that started a turn, for every agent. A message you send while the agent is still answering joins that turn. Claude's CLI holds it until a break and records it after the reply; Orca's chat shows it where you sent it. Rewinding to such a message would cut the chat and Claude's conversation in different places, and for Codex it also removed the message before it. So the Rewind action now appears only on messages that started a turn (the common pattern), the host refuses the others (older clients included), and a mid-reply message is kept or removed together with its turn.
A rewound chat whose history is in another Claude account gets the same plain "history is in another account" message a normal resume gets, instead of Claude's raw start error.
It also deletes the dead September proof code: the "intentional rewind" branch of the conversation-file reader, and its test.
Why
Fork, not resuming the same session at an earlier point. Resuming the same session at an earlier point leaves the removed turns at the end of Claude's conversation file until the next turn is written. On restart, Orca rebuilds a Claude chat's history from the last point it recorded up to the end of that file, so a restart could bring removed turns back. A fork writes a new file and leaves the old one as it was.
The SDK's fork options inside the chat's own Claude process, not the SDK's separate fork call. The separate call can't be pointed at the chat's Claude account, and Orca runs each account through its own config folder. It also gives every copied message a new id, which would break the point Orca records for rebuilding history on restart. The option-based fork keeps each copied message's id. The real-CLI test checks this.
The fork happens on the next launch, not during the rewind. Claude writes the forked conversation only when the first message is sent. The launch already decides between "start" and "resume" by checking whether the conversation file exists, so "fork pending" is worked out from disk each time and ends by itself.
Differences from the common pattern, labelled:
The cut is found by reading the message's parent in Claude's file, not recorded live at the end of each turn. Intended. Orca's saved rows are keyed by streaming-event ids, not by the ids in Claude's file, so no saved row names a usable cut. The lookup also works for chats recorded before this change.
No "drop this turn" guard (
resumeDropsTurn). Intended. The SDK says a refusal from it should clear the pending fork and resume normally. By the time the fork launches, Orca has already rewound the chat, so there would be no honest way back.A running turn is refused, not stopped first. Intended. This is the same rule Codex rewind uses.
No Claude CLI version check. Intended. Forking into a fixed session id needs Claude Code 2.0.73 (released about December 2025), and other parts of Orca's Claude chat already need the 2.1 line. A check before every launch could wrongly refuse a rewound chat whenever the version lookup is slow.
Temporary: Opening a chat in a terminal while a rewind is in flight gives the conversation as of the click; follow-up only if users hit it.
Linked Issue
Builds on #30394, which builds a rewind's kept history from Orca's own saved rows; this PR's Claude branch reuses that code. This branch contains #30394's commit (
dc70d610b40, "rewind works at any chat length") so CI can run againstmain; review only the commits after it. Merge #30394 first.Visual Proof
Real app, background test copy, real signed-in Codex (QA at
ddbcba5; the later commit changes only Claude code).Before, on main: a Claude chat's message offers no Rewind action (the test copy's Claude wasn't signed in, so this shows only the missing action).
Codex rewind still works. Three turns (ALPHA, BRAVO, CHARLIE), the confirm dialog, the chat after rewinding to BRAVO (only ALPHA kept, BRAVO back in the composer), and Codex answering "ALPHA" when asked which words it was asked for.
Claude rewind in the real app, final commit
59990b9(Windows, real Claude and Codex). Rewinding to BRAVO keeps only ALPHA, puts BRAVO back in the composer, and Claude then recalls only ALPHA:A message sent while the agent is still answering offers no Rewind (only Copy); the message that started the turn does, and rewinding to it removes the whole turn:
Codex follows the same rule, and its plain rewind still works:
An earlier full run at
e73b2d4(M4Air, real Claude) also passed restart before and after sending, rewinding twice, rewinding past/compact, tool rows kept, "Open in terminal" refusal then success, and Session History.Testing
ORCA_REAL_CLAUDE_CLI_TEST=1,claude-structured-real-cli-rewind.test.ts(new). It drives Orca's own Claude adapter through two turns, then checks:structured-agent-session-fork-rewind.test.ts, new): a Claude rewind asks the adapter for the fork, saves it, stops the agent, and rebuilds the history from the rows before the message, without calling the provider.structured-claude-pending-rewind.test.ts): a rewind interrupted after its record was saved launches the fork, and an older build's pending rewind is still refused.Notes