Skip to content

fix(rewind): skip turns Claude never answered when finding the turn before - #68

Merged
ziyadakl merged 2 commits into
custom-v2from
fix/rewind-skips-empty-turns
Nov 14, 2026
Merged

ziyadakl merged 2 commits into
custom-v2from
fix/rewind-skips-empty-turns

Conversation

@ziyadakl

Copy link
Copy Markdown
Owner

Summary

Rewind refused a message when the turn just before it was a stopped turn Claude never answered: the messages typed mid-answer were folded into the earlier run, which left empty cancelled runs. The error blamed "the current Claude session" even though the chat had only one.

 previousConversationRun(projection, run)
   earlier runs, in conversation order
     not rolled_back
+    and providerTurnForRun(run) exists   # skip runs Claude never answered
   → last one

Both users of it now agree: the refusal check (ThreadRewindCommands.ts) and the rollback target (ThreadRewindService.ts). They share one exported providerTurnForRun in ThreadRewindTargets.ts, so the skip and the refusal can't drift.

Real case (affinity-vps, 2026-11-14): runs 29 completed → 30, 31 cancelled with no turn and 0 messages → 32 completed. Rewinding 32 was refused. It now rolls back to 29's turn.

Closes #67

Evidence

  • Before: the new test rewinds past cancelled turns Claude never answered, back to the last answered turn fails with Cannot rewind to this message: the turn before it is not part of the current Claude session. (Tests 1 failed | 2 passed)
    After: ThreadRewind, ThreadRewindService, AgentSessionImporter: Tests 23 passed (23). The test drains the effect worker and asserts one provider_turn rollback at the answered run, its lastRunOrdinal, and runs after it rolled_back. It goes red when the skip is removed from ThreadRewindService.
  • A different provider session still refuses: kept by its own test.
  • vp run check:changed: format, lint, knip, typecheck and react compiler pass locally.

Merge Danger

Door: two-way

Server-only logic change; revert the merge to undo. No data migration.

Blast Radius: rewind

Only rewinds that used to be refused behave differently. In those, unanswered runs between the target and the rewound message are now marked rolled back, which hides any message left on them. Claude never saw such a message.

🤖 Generated with Claude Code

… turn before

previousConversationRun returned the nearest earlier run that was not rolled
back, even a cancelled one with no provider turn. providerTurnForRun then
returned undefined and thread.rewind refused with "the turn before it is not
part of the current Claude session". It now skips runs with no provider turn,
so the rewind resumes at the last answered run. A previous turn on a different
provider thread still refuses.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…iderTurnForRun for skip and refusal

The skip test now drains the effect worker and checks the provider rolled back
to the answered run, lastRunOrdinal is that run's, and the cancelled runs and
the rewound run end rolled_back. Red when the provider-turn skip is removed
from ThreadRewindService.

previousConversationRun and the rewind refusal now share one exported
providerTurnForRun in ThreadRewindTargets, replacing runHasProviderTurn and the
copy injected from Orchestrator, so they cannot drift. Orchestrator only stops
passing the dependency.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M labels Nov 14, 2026
@ziyadakl
ziyadakl merged commit 2880023 into custom-v2 Nov 14, 2026
27 checks passed
@ziyadakl
ziyadakl deleted the fix/rewind-skips-empty-turns branch November 14, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 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.

2 participants