Skip to content

Rewind Claude chats by forking the kept conversation - #30400

Open
brennanb2025 wants to merge 25 commits into
mainfrom
brennanb2025/chat-rewind-availability-repro
Open

brennanb2025 wants to merge 25 commits into
mainfrom
brennanb2025/chat-rewind-availability-repro

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​421 $\color{#cf222e}{\Huge{\mathbf{−}}}$​69 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​352
Prod 20 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​424 $\color{#cf222e}{\Huge{\mathbf{−}}}$​90 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​334

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.

  1. 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.

  2. 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.

  3. 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 fixed sessionId). 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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 against main; 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).

baseline-claude-no-rewind.png

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.

codex-before-rewind.png
codex-confirm-rewind.png
codex-rewound-composer.png
codex-memory-after-rewind.png

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:

final-flow1-rewound-composer.png
final-flow1-memory.png

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:

final-flow2-steer-hover.png
final-flow2-opener-hover.png
final-flow2-rewound-composer.png

Codex follows the same rule, and its plain rewind still works:

final-flow4-steer-hover.png
final-flow5-rewound-composer.png

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

  • Real Claude CLI (2.1.302), 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:
    • the prompt id Orca sends is the one in Claude's file;
    • the parent lookup finds the cut;
    • a fork launch there forgets the second turn;
    • the fork keeps the copied entries' ids;
    • the rewound conversation's file is byte-for-byte unchanged.
  • Host flow (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.
  • Crash safety (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.
  • The existing Codex rewind and launch tests pass unchanged.
  • I manually tested these changes locally (QA run pending)
  • Automated tests added/updated

Notes

  • SSH and remote: the cut lookup and the fork launch both run on the host that runs Claude.
  • Mixed versions: the Rewind flag already reaches clients through the options read, so older desktop clients and mobile show it with no change on the wire.
  • Folder workspaces: nothing here assumes a git worktree.
  • Windows: no new processes or paths beyond Claude's own config folders.

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.
@brennanb2025
brennanb2025 changed the base branch from brennanb2025/chat-rewind-length to main October 18, 2026 19:06
@brennanb2025
brennanb2025 marked this pull request as ready for review October 18, 2026 19:07
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e2966877-1a61-429f-85d6-228d9eaca401
📥 Commits

Reviewing files that changed from the base of the PR and between ef77d76 and ddbcba5.

📒 Files selected for processing: 6
  • src/main/claude/claude-structured-session-acquisition.ts
  • 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
  • src/shared/agent-session-rewind.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

📝 Walkthrough

Claude structured sessions now support rewind by forking at a selected message in the transcript. The rewind record stores fork details for launch and recovery. The launch can resume from the source session at the retained entry and records the replaced provider handle. The host also supports message-based cuts while retaining the turn-based rewind path. A CLI fork is rejected when a pending rewind replaces the current provider handle.

Priority: ➖ Normal

Merge Risk: 🟡 Moderate · up to ddbcb

A rewind may fail repeatedly when its source session is in a different Claude home, or retain journal rows absent from the forked conversation. Resolve or explicitly accept these risks before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 48.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 47 functions across 37 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title is concise, specific, and accurately describes the main change: implementing Claude chat rewind by forking the retained conversation.
Description check Passed The description is complete and specific. It includes ELI5, user-visible behavior, implementation details, rationale, dependency context, visual proof, testing results, and platform notes. It does not…
Full details: Description check

Explanation

The description is complete and specific. It includes ELI5, user-visible behavior, implementation details, rationale, dependency context, visual proof, testing results, and platform notes. It does not include the full checklist, the agent skill boundary section, or an explicit Fixes # issue link, but these omissions do not prevent review because the required change context is documented and an issue dependency is identified.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e7736a6d-127c-43f1-8995-ba6650ec0498
📥 Commits

Reviewing files that changed from the base of the PR and between 3af9c22 and 0b90e58.

📒 Files selected for processing: 42
  • src/main/ai-vault/structured-chat-cli-fork.ts
  • src/main/claude/claude-rewind-fork.ts
  • src/main/claude/claude-structured-agent-definition.ts
  • src/main/claude/claude-structured-launch-resolution.ts
  • src/main/claude/claude-structured-owner-identity.ts
  • src/main/claude/claude-structured-real-cli-rewind.test.ts
  • src/main/claude/claude-structured-session-acquisition.ts
  • src/main/claude/claude-structured-session-adapter.ts
  • src/main/claude/claude-structured-session-id.ts
  • src/main/claude/claude-structured-session-publication.ts
  • src/main/claude/claude-transcript-branch-graph.ts
  • src/main/claude/claude-transcript-rewind-proof.test.ts
  • src/main/codex/codex-structured-rewind.test.ts
  • src/main/codex/codex-structured-rewind.ts
  • src/main/native-chat/agent-session-journal/journal-item-provenance.spec.ts
  • src/main/native-chat/agent-session-wire/structured-agent-registry.ts
  • 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-adapter-router-test-support.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter-router.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-adapter.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-attach-orchestration.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-clear-rewind.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-fork-rewind.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-provider-started.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind-at-rest.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind-live-send.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind-provenance.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind-replay.test-fixture.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind-stop-note.test-fixture.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind.test.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-rewind.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-send-preparation.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-cut.test.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-cut.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-journal-body.test.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-journal-body.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-recovery.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.test.ts
  • src/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.ts
  • src/main/runtime/agent-session-rewind-completion.ts
  • src/main/runtime/structured-claude-pending-rewind.test.ts
  • src/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.

Comment thread src/main/claude/claude-rewind-fork.ts
Comment thread src/main/native-chat/agent-session-wire/structured-rewind-cut.ts
@brennanb2025

brennanb2025 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

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 8f52d21f9e55bfccd40958178496ffa30df6e261, with no new proven findings in the final integration check. Round 6 remains the final source review; this closes out the subsequent main merges.

  • Fixed: idle Claude rewind avoids unnecessary startup; account routing uses the existing other-account refusal; restored Codex opening messages retain Rewind; previously saved rewind operations can settle after updating. Eligibility is checked for new actions, separately from saved-operation recovery.
  • Deferred: overlapping terminal requests use the conversation as of the click (C2, temporary difference labelled in the PR description). Generic refusal wording before the fork file exists remains P3 (C4). No open P0/P1 findings.
  • Main integration: both subsequent commits are clean merges. Independently comparing the reviewed patch, excluding the eight formatting-only files now supplied by main, with the final patch gives the same 27 files and identical added/removed lines. Checked main's late dispatch settlement, Claude usage-limit journaling and turn identity, send admission, and test manifest changes against rewind cuts and saved-operation recovery. No semantic conflict found; the shared eligibility predicate and renderer rewind implementation are unchanged.
  • Tests: all 14 tracked rewind test files except the real-CLI suite passed on this exact head through the test queue: 126 tests passed. Earlier round 6 also passed 162 tests in 16 named files and the production-source recovery/history reproductions. No new real-CLI contract run, app launch or runtime performance measurement was performed in this closeout.
  • CI and review state: PR workflow 39335514351, .github/workflows/pr.yml, succeeded on exact 8f52d21; its PR checks passed aggregate (job 118351606015) succeeded. All current checks are successful or skipped, both review threads are resolved, and the PR is out of draft with CLEAN merge status.
  • Real QA carried forward: QA615 passed four real Claude/Codex flows on Windows at 59990b9: basic rewind and follow-up memory, suppression and host refusal of mid-reply targets, and whole-turn rewind from the opener. Its net PR change is identical to this head, with main interactions checked above. QA603 supplies the earlier main comparison and restart, double rewind, compact, tools, terminal handoff and Session History coverage. Its earlier mid-reply failure led to the whole-turn policy verified by QA615. The conditional queued-own-turn scenario was unavailable because the build lacked its capability/control; it is not claimed as passed. No rendered QA on 8f52d21 is claimed.

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): Main: no Claude Rewind QA615 Claude rewind and follow-up memory: Claude prompt restored Only kept history recalled Mid-reply target and whole-turn result: Mid-reply message offers Copy only Opening prompt restored after whole-turn rewind Codex comparison: Codex mid-reply message offers Copy only Codex prompt restored The three judgment points are opening-message identity, new-action admission, and saved-operation recovery.

… 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.

This branch has not been deployed

No deployments
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