Skip to content

fix/cancel-preserve-progress#182

Merged
omarluq merged 4 commits into
mainfrom
fix/cancel-preserve-progress
Jul 2, 2026
Merged

fix/cancel-preserve-progress#182
omarluq merged 4 commits into
mainfrom
fix/cancel-preserve-progress

Conversation

@omarluq

@omarluq omarluq commented Jul 1, 2026

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6121da6a-733a-4997-958e-6bdb65624570

📥 Commits

Reviewing files that changed from the base of the PR and between 7771a8e and b17db29.

📒 Files selected for processing (2)
  • internal/terminal/prompt_response.go
  • internal/terminal/prompt_response_internal_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/terminal/prompt_response_internal_test.go
  • internal/terminal/prompt_response.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Canceling now reliably preserves already-streamed partial assistant/tool progress, persists it, and finishes with consistent “response canceled; progress saved” messaging.
    • Improved tool-progress persistence on partial failures, including correct handling of in-flight tool calls and avoiding duplicated cancellation markers.
    • Stale/mismatched prompt responses are ignored; queued prompts and compaction updates are applied correctly, and cancellation is idempotent.
    • Rendering of canceled progress on prompt errors now behaves consistently.
  • Tests
    • Expanded coverage for cancellation, queued-prompt handling, compaction behavior, and pending tool replacement/removal during streaming.

Walkthrough

The PR updates assistant partial-prompt persistence to track pending tool calls and synthesize failure messages, and changes terminal prompt cancellation and response handling so canceled prompts keep their progress state.

Changes

Assistant Runtime Persistence

Layer / File(s) Summary
Persistence context for session resolution
internal/assistant/runtime.go
Runtime.Prompt derives a persistence context and uses it for session resolution and user-entry appending.
Pending tool call tracking
internal/assistant/runtime_persist.go
partialPromptProgress now tracks pending tool calls, updates them on tool start and tool result events, and clears them on reset while cloning persistable blocks.
Synthetic failure persistence
internal/assistant/runtime_persist.go
Partial-prompt failure persistence now uses a timeout-bound context and appends original blocks, synthetic tool-result failures, and a final custom error message derived from the prompt error.
Partial progress and runtime tests
internal/assistant/runtime_persist_internal_test.go, internal/assistant/runtime_test.go
Runtime tests cover pending-tool replacement, reset behavior, cancellation markers, synthetic tool-result placeholders, and updated completion helpers and assertions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Terminal Prompt Cancellation Flow

Layer / File(s) Summary
Remove canceled prompt state
internal/terminal/app.go
activePromptState no longer stores BaselineMessages, and App no longer keeps or initializes the canceledPrompts map.
Mark canceled prompts only
internal/terminal/prompt_cancel.go
cancelActivePrompt now only marks the active prompt canceled, calls its cancel function, and updates status with an idempotency guard.
Async event and response handling
internal/terminal/async_events.go, internal/terminal/prompt_response.go, internal/terminal/prompt_send.go
Prompt event filtering, stream handling, user-entry updates, prompt-error handling, prompt-response handling, and prompt sending now rely on the active prompt state directly and preserve canceled progress while processing queued prompts.
Cancellation and response tests
internal/terminal/prompt_cancel_internal_test.go, internal/terminal/async_events_internal_test.go, internal/terminal/interrupt_internal_test.go, internal/terminal/prompt_response_internal_test.go, internal/terminal/prompt_send_internal_test.go, internal/terminal/render_internal_test.go
Terminal tests are updated for the new cancellation flow, including progress preservation, idempotent cancelation, context-aware completion mocks, and canceled-progress assertions.

Estimated code review effort: 4 (Complex) | ~55 minutes

Possibly related PRs

  • omarluq/librecode#42: Both PRs modify assistant partial-progress persistence and tool-result handling.
  • omarluq/librecode#47: Both PRs extend runtime tests around partial prompt failure persistence.
  • omarluq/librecode#69: Both PRs change terminal prompt cancellation behavior and related state handling.

Poem

A rabbit hops, then pauses in delight,
progress stays tucked safe and bright.
Tools may stall, yet trails remain,
and canceled prompts still bear their name.
🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No description was provided, so there is no meaningful summary to evaluate. Add a brief description of the cancellation/progress-preservation changes and their intent.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: preserving prompt progress when canceling.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cancel-preserve-progress

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

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.54054% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.93%. Comparing base (84da8b9) to head (b17db29).

Files with missing lines Patch % Lines
internal/assistant/runtime_persist.go 86.45% 9 Missing and 4 partials ⚠️
internal/terminal/async_events.go 95.45% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #182      +/-   ##
==========================================
+ Coverage   82.89%   82.93%   +0.04%     
==========================================
  Files         287      287              
  Lines       23127    23166      +39     
==========================================
+ Hits        19171    19213      +42     
+ Misses       2799     2796       -3     
  Partials     1157     1157              
Flag Coverage Δ
unittests 82.93% <90.54%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/terminal/prompt_response.go (1)

11-31: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the prompt ID/canceled guard before applying a response.

Line 11 discards promptID, so a canceled prompt that still returns a non-nil response can be committed as a normal assistant response. Preserve progress, but do not accept responses for missing, mismatched, or canceled active prompts.

Proposed fix
-func (app *App) applyPromptResponse(ctx context.Context, response *assistant.PromptResponse, _ uint64) {
+func (app *App) applyPromptResponse(ctx context.Context, response *assistant.PromptResponse, promptID uint64) {
 	streamingBlocks := append([]chatMessage(nil), app.transcript.Streaming.Blocks...)
+	if app.activePrompt == nil || app.activePrompt.ID != promptID {
+		return
+	}
+	if app.activePrompt.Canceled {
+		app.working = false
+		app.streamingText = ""
+		app.streamingThinkingText = ""
+		app.resetStreamingBlocks()
+		app.streamedToolEvents = 0
+		app.activePrompt = nil
+		app.applyFailedPromptStreamedBlocks(streamingBlocks)
+		app.setStatus("response canceled; progress saved")
+		app.processQueuedPrompt(ctx)
+
+		return
+	}
+
 	app.working = false
 	app.streamingText = ""
 	app.streamingThinkingText = ""
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/prompt_response.go` around lines 11 - 31, In
applyPromptResponse, keep the existing promptID/canceled validation before
committing any non-nil response so stale or canceled prompts are not applied as
assistant messages. Preserve the progress/state updates already needed, but only
call app.applyPromptResponseSideEffects, app.applyTokenUsage, and app.addMessage
after confirming the response still matches the current active prompt and has
not been canceled; otherwise discard it and continue with queued prompts.
🧹 Nitpick comments (1)
internal/assistant/runtime_test.go (1)

676-683: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider table-driven consolidation for the four new completer variants and their tests.

partialFailureCompleter, canceledPartialFailureCompleter, pendingToolFailureCompleter, pendingCanceledToolFailureCompleter, and completedToolFailureCompleter all follow the identical Complete() shape, differing only by which emit* helpers are called and which error is returned. Same for the four test functions at lines 315-387, which share the "run Prompt, fetch messages, assert" structure. This is a strong candidate for a single table-driven test with a struct-per-case (event sequence, error, expected message assertions), reducing duplication across five near-identical types and four near-identical tests.

As per coding guidelines, "Prefer table-driven tests for core behavior and regression tests for terminal rendering bugs in Go."

Also applies to: 735-786

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/assistant/runtime_test.go` around lines 676 - 683, Consolidate the
duplicated completer variants and their tests into table-driven forms: the
`partialFailureCompleter`, `canceledPartialFailureCompleter`,
`pendingToolFailureCompleter`, `pendingCanceledToolFailureCompleter`, and
`completedToolFailureCompleter` types all implement the same `Complete()`
pattern with only helper calls and returned errors changing, so replace them
with a shared case-driven implementation keyed by the existing `Complete`
behavior. Likewise, merge the four near-identical test functions into one
table-driven test that exercises `Prompt`, collects messages, and asserts
per-case expectations using a struct describing the event sequence, error, and
expected message checks.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/assistant/runtime_persist.go`:
- Around line 276-291: The trackPendingTool method on partialPromptProgress
currently appends a pending tool even when ToolCallEvent is nil and fallbackName
is empty, which later creates an unidentifiable synthetic result. Update
trackPendingTool to skip adding anything when both call.Name and fallbackName
are empty, while preserving the existing fallback behavior for named tools and
keeping pendingTools consistent.

In `@internal/assistant/runtime_test.go`:
- Around line 371-387: The test name suggests a canceled prompt scenario, but
the current setup in
TestRuntime_PromptDoesNotDuplicateCanceledResultForCompletedTool uses
completedToolFailureCompleter, which returns a generic error instead of
context.Canceled. Update this test to use the cancellation path (for example, a
completer that returns context.Canceled, like
pendingCanceledToolFailureCompleter) so the deduplication behavior is verified
under the actual canceled-and-completed tool case. Keep the existing assertions
against runtime.Prompt and repository.Messages, and ensure the checked
ToolResult content still excludes the canceled text.

In `@internal/terminal/async_events.go`:
- Line 230: The async event filter is incorrectly gating compaction events on
activePrompt, which causes asyncEventCompactStart/Done/Error to be dropped when
no prompt is active. Update isPromptAsyncEvent in async_events.go so
compaction-related payloads are handled using activeCompaction (or bypass the
activePrompt check for those event types), while keeping prompt events filtered
by PromptID. Make the logic branch on the event type before applying the
activePrompt guard so compaction completion and error events always reach the UI
state machine.
- Around line 489-505: The prompt error path in applyPromptError clears working
state but never drains queued follow-ups, so a queued prompt can remain stuck
after cancellation/error handling. Update applyPromptError to accept ctx like
the surrounding prompt flow, and after the status/message update call the
queue-draining path used by sendPrompt/processQueuedPrompt so any pending prompt
in queuedMessages is resumed once the terminal prompt finishes.

---

Outside diff comments:
In `@internal/terminal/prompt_response.go`:
- Around line 11-31: In applyPromptResponse, keep the existing promptID/canceled
validation before committing any non-nil response so stale or canceled prompts
are not applied as assistant messages. Preserve the progress/state updates
already needed, but only call app.applyPromptResponseSideEffects,
app.applyTokenUsage, and app.addMessage after confirming the response still
matches the current active prompt and has not been canceled; otherwise discard
it and continue with queued prompts.

---

Nitpick comments:
In `@internal/assistant/runtime_test.go`:
- Around line 676-683: Consolidate the duplicated completer variants and their
tests into table-driven forms: the `partialFailureCompleter`,
`canceledPartialFailureCompleter`, `pendingToolFailureCompleter`,
`pendingCanceledToolFailureCompleter`, and `completedToolFailureCompleter` types
all implement the same `Complete()` pattern with only helper calls and returned
errors changing, so replace them with a shared case-driven implementation keyed
by the existing `Complete` behavior. Likewise, merge the four near-identical
test functions into one table-driven test that exercises `Prompt`, collects
messages, and asserts per-case expectations using a struct describing the event
sequence, error, and expected message checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 94c35756-ffe1-445c-b077-5d9b53fb5048

📥 Commits

Reviewing files that changed from the base of the PR and between 84da8b9 and 633d237.

📒 Files selected for processing (15)
  • internal/assistant/runtime.go
  • internal/assistant/runtime_persist.go
  • internal/assistant/runtime_persist_internal_test.go
  • internal/assistant/runtime_test.go
  • internal/terminal/app.go
  • internal/terminal/async_events.go
  • internal/terminal/async_events_internal_test.go
  • internal/terminal/interrupt_internal_test.go
  • internal/terminal/prompt_cancel.go
  • internal/terminal/prompt_cancel_internal_test.go
  • internal/terminal/prompt_response.go
  • internal/terminal/prompt_response_internal_test.go
  • internal/terminal/prompt_send.go
  • internal/terminal/prompt_send_internal_test.go
  • internal/terminal/render_internal_test.go
💤 Files with no reviewable changes (1)
  • internal/terminal/prompt_response_internal_test.go

Comment thread internal/assistant/runtime_persist.go
Comment thread internal/assistant/runtime_test.go Outdated
Comment thread internal/terminal/async_events.go
Comment thread internal/terminal/async_events.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/terminal/prompt_response_internal_test.go (1)

146-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

LGTM! Solid coverage of the canceled-preserve-progress path.

Consider also adding a case for Canceled=true with a nil response (a plausible caller path, since applyPromptResponse is invoked with response == nil elsewhere in this same test file) to lock in that finishPrompt/applyFailedPromptStreamedBlocks still run safely without touching response.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/prompt_response_internal_test.go` around lines 146 - 166,
The canceled-progress test in applyPromptResponse only covers a non-nil response
path, so add a companion case where activePrompt.Canceled is true and the
response passed to applyPromptResponse is nil. Reuse the existing terminal
prompt test setup around TestApplyPromptResponsePreservesCanceledProgress and
assert that finishPrompt and applyFailedPromptStreamedBlocks still complete
safely, with no attempt to read response content and the same preserved-progress
state transitions.
internal/terminal/prompt_response.go (1)

17-24: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Canceled branch silently drops usage/session data from a completed response.

When app.activePrompt.Canceled is true but response is non-nil (the request actually finished on the backend), this branch never calls app.applyTokenUsage(&response.Usage) or updates app.sessionID. If the model call completed and consumed tokens despite the UI cancellation, that usage is lost from the running total. Given the PR's goal is to "preserve progress," consider whether usage/session bookkeeping should also be preserved for late-but-real responses (guarding for response != nil since this function is also invoked with a nil response).

Possible adjustment
 	if app.activePrompt.Canceled {
 		app.finishPrompt()
 		app.applyFailedPromptStreamedBlocks(streamingBlocks)
+		if response != nil {
+			app.sessionID = response.SessionID
+			app.applyTokenUsage(&response.Usage)
+		}
 		app.setStatus("response canceled; progress saved")
 		app.processQueuedPrompt(ctx)
 
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/terminal/prompt_response.go` around lines 17 - 24, The canceled path
in prompt_response handling is dropping late response bookkeeping even when the
backend returned a real response. Update the app.activePrompt.Canceled branch in
the prompt response flow to preserve usage and session state by calling
app.applyTokenUsage(&response.Usage) and updating app.sessionID when response is
non-nil, while still guarding against nil responses before accessing response
fields. Keep the existing finishPrompt, applyFailedPromptStreamedBlocks,
setStatus, and processQueuedPrompt behavior intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/terminal/prompt_response_internal_test.go`:
- Around line 146-166: The canceled-progress test in applyPromptResponse only
covers a non-nil response path, so add a companion case where
activePrompt.Canceled is true and the response passed to applyPromptResponse is
nil. Reuse the existing terminal prompt test setup around
TestApplyPromptResponsePreservesCanceledProgress and assert that finishPrompt
and applyFailedPromptStreamedBlocks still complete safely, with no attempt to
read response content and the same preserved-progress state transitions.

In `@internal/terminal/prompt_response.go`:
- Around line 17-24: The canceled path in prompt_response handling is dropping
late response bookkeeping even when the backend returned a real response. Update
the app.activePrompt.Canceled branch in the prompt response flow to preserve
usage and session state by calling app.applyTokenUsage(&response.Usage) and
updating app.sessionID when response is non-nil, while still guarding against
nil responses before accessing response fields. Keep the existing finishPrompt,
applyFailedPromptStreamedBlocks, setStatus, and processQueuedPrompt behavior
intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 30ec0ab2-337c-44f1-a78f-f427066361ba

📥 Commits

Reviewing files that changed from the base of the PR and between 633d237 and 7771a8e.

📒 Files selected for processing (7)
  • internal/assistant/runtime_persist.go
  • internal/assistant/runtime_test.go
  • internal/terminal/async_events.go
  • internal/terminal/async_events_internal_test.go
  • internal/terminal/prompt_response.go
  • internal/terminal/prompt_response_internal_test.go
  • internal/terminal/render_internal_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/terminal/render_internal_test.go
  • internal/terminal/async_events.go
  • internal/assistant/runtime_test.go
  • internal/assistant/runtime_persist.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@omarluq omarluq merged commit c65172c into main Jul 2, 2026
16 checks passed
@omarluq omarluq deleted the fix/cancel-preserve-progress branch July 2, 2026 01:06
@coderabbitai coderabbitai Bot mentioned this pull request Jul 14, 2026
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