Skip to content

feat: rewind-on-interrupt — clean conversation state on abort/resume#11284

Open
hannesrudolph wants to merge 2 commits intomainfrom
feat/rewind-on-interrupt
Open

feat: rewind-on-interrupt — clean conversation state on abort/resume#11284
hannesrudolph wants to merge 2 commits intomainfrom
feat/rewind-on-interrupt

Conversation

@hannesrudolph
Copy link
Collaborator

@hannesrudolph hannesrudolph commented Feb 7, 2026

Summary

When a user interrupts a task (abort or close VS Code) and later resumes it, the conversation history could contain corrupted state: orphaned tool_use blocks without matching tool_results, synthetic "interrupted" messages that confused the LLM, and attempt_completion calls that never received a response. This PR introduces a rewind mechanism that truncates conversation state to the last valid checkpoint on both abort and resume, ensuring the LLM always sees a clean, API-compliant conversation.

Changes

Core Implementation

  • rewindToLastAskPoint() in Task.ts — truncates clineMessages to the last qualifying ask point, then sanitizes trailing API history (removes orphaned assistant tool_use blocks, incomplete user tool_result messages)
  • Two-phase completion flow in AttemptCompletionTool.tscompletion_result ask followed by resume_completed_task ask holds execution context alive, ensuring attempt_completion always gets a tool_result
  • getPendingAttemptCompletionToolUseId() in Task.ts — detects pending attempt_completion tool_use for routing follow-up text as tool_result on resume
  • Neutral fallback message in validateToolResultIds.ts — "No result available for this tool call." replaces the old "Tool execution was interrupted" synthetic message

Code Quality

  • Extracted normalizeContentBlocks() to src/shared/messages.ts — replaces 5 inline Array.isArray(content) patterns
  • Type predicates replace 3 post-hoc as casts on Array.filter()/Array.find() results
  • satisfies checks replace 2 as Anthropic.ToolResultBlockParam casts on conforming object literals
  • Eliminated ! non-null assertions — direct truthiness checks on pendingAttemptCompletionToolUseId let TypeScript narrow naturally

Tests (65 total)

  • 12 unit tests for rewindToLastAskPoint() covering truncation, ask-type filtering, API history cleanup, error handling, idempotency
  • 7 integration tests including completion→restart→resume with/without follow-up, double rewind idempotency, multi-tool_use edge case
  • 15 completion flow tests for AttemptCompletionTool covering accept, feedback, and abort paths
  • 5 utility tests for normalizeContentBlocks()
  • Persistence assertions verify overwriteClineMessages/overwriteApiConversationHistory receive correct truncated data
  • Shared test fixtures in rewind-test-setup.ts reduce ~110 lines of duplicated mock setup

Important

Introduces a rewind mechanism to clean conversation state on task abort/resume, ensuring consistent conversation history by truncating to the last valid checkpoint.

  • Behavior:
    • rewindToLastAskPoint() in Task.ts truncates clineMessages to the last valid ask point, sanitizing API history by removing orphaned tool_use blocks and incomplete tool_result messages.
    • Two-phase completion flow in AttemptCompletionTool.ts ensures attempt_completion always gets a tool_result by asking completion_result followed by resume_completed_task.
    • getPendingAttemptCompletionToolUseId() in Task.ts detects pending attempt_completion tool_use for routing follow-up text as tool_result on resume.
    • Neutral fallback message in validateToolResultIds.ts replaces synthetic "interrupted" messages with "No result available for this tool call."
  • Code Quality:
    • Extracted normalizeContentBlocks() to messages.ts to replace inline Array.isArray(content) patterns.
    • Type predicates replace post-hoc as casts on Array.filter()/Array.find() results.
    • satisfies checks replace as Anthropic.ToolResultBlockParam casts on conforming object literals.
    • Eliminated ! non-null assertions by using direct truthiness checks.
  • Tests (65 total):
    • 12 unit tests for rewindToLastAskPoint() covering truncation, ask-type filtering, API history cleanup, error handling, idempotency.
    • 7 integration tests including completion→restart→resume scenarios, double rewind idempotency, multi-tool_use edge case.
    • 15 completion flow tests for AttemptCompletionTool covering accept, feedback, and abort paths.
    • 5 utility tests for normalizeContentBlocks().
    • Persistence assertions verify overwriteClineMessages/overwriteApiConversationHistory receive correct truncated data.
    • Shared test fixtures in rewind-test-setup.ts reduce ~110 lines of duplicated mock setup.

This description was created by Ellipsis for 1f04bab. You can customize this summary. It will automatically update as commits are pushed.

- Add rewindToLastAskPoint() to truncate clineMessages and sanitize API histories
- Add two-phase completion flow (completion_result → resume_completed_task)
- Extract normalizeContentBlocks() utility to src/shared/messages.ts
- Replace 'as' casts with type predicates and 'satisfies' checks
- Remove synthetic 'interrupted' tool_result injection
- Neutral fallback message in validateToolResultIds
- Comprehensive test coverage: 65 tests across 5 files
- Shared test fixtures in rewind-test-setup.ts
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. Enhancement New feature or request labels Feb 7, 2026
@roomote
Copy link
Contributor

roomote bot commented Feb 7, 2026

Oroocle Clock   See task

All previously flagged issues have been addressed. No new issues found in the latest changes.

  • getPendingAttemptCompletionToolUseId should guard against multi-tool_use assistant messages where other tool_uses also lack tool_results, to avoid bypassing the rewind when the task wasn't fully completed
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment on lines 2127 to 2132
const toolUse = content.find(
(block): block is Anthropic.Messages.ToolUseBlockParam =>
block.type === "tool_use" && block.name === "attempt_completion",
)

return toolUse?.id
Copy link
Contributor

Choose a reason for hiding this comment

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

If the last assistant message contains attempt_completion alongside other tool_use blocks (e.g., read_file + attempt_completion) and the process crashes before any tool_result is saved, this method returns the attempt_completion ID, which causes resumeTaskFromHistory to bypass the rewind and treat the task as completed. The other tool_uses never executed, so validateAndFixToolResultIds patches in synthetic "No result available" fallbacks, producing misleading context for the model. This is unlikely in practice (models rarely combine attempt_completion with other tools), but the guard is straightforward:

Suggested change
const toolUse = content.find(
(block): block is Anthropic.Messages.ToolUseBlockParam =>
block.type === "tool_use" && block.name === "attempt_completion",
)
return toolUse?.id
const toolUse = content.find(
(block): block is Anthropic.Messages.ToolUseBlockParam =>
block.type === "tool_use" && block.name === "attempt_completion",
)
// Only treat as pending completion if there are no other unanswered
// tool_use blocks — otherwise the rewind path is more appropriate.
if (toolUse) {
const hasOtherToolUse = content.some(
(block) => block.type === "tool_use" && block !== toolUse,
)
if (hasOtherToolUse) return undefined
}
return toolUse?.id

Fix it with Roo Code or mention @roomote and request a fix.

…e messages

When the last assistant message contains attempt_completion alongside
other tool_use blocks (e.g. read_file + attempt_completion) and the
process crashes before any tool_result is saved, the method previously
returned the attempt_completion ID. This caused resumeTaskFromHistory
to bypass the rewind and treat the task as completed, while the other
tool_uses never executed.

Now returns undefined when other tool_use blocks are present alongside
attempt_completion, allowing the rewind path to handle the cleanup
correctly.

Addresses review feedback from @roomote.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant