Skip to content

fix(tui): stop thinking spinner leaking past turn end on empty deltas#97

Merged
liruifengv merged 6 commits into
MoonshotAI:mainfrom
shuizhongyueming:fix/thinking-spinner-not-stopping
May 27, 2026
Merged

fix(tui): stop thinking spinner leaking past turn end on empty deltas#97
liruifengv merged 6 commits into
MoonshotAI:mainfrom
shuizhongyueming:fix/thinking-spinner-not-stopping

Conversation

@shuizhongyueming
Copy link
Copy Markdown
Contributor

Related Issue

Resolve #96

Problem

When an AI provider emits an empty thinking delta (e.g. Anthropic signature_deltathink: ""), the TUI creates a ThinkingComponent with a running spinner but no text. When the thinking phase ends, flushThinkingToTranscript sees an empty thinkingDraft and returns early without stopping the spinner, causing it to animate indefinitely.

What changed

  • onThinkingUpdate: skip component creation when text is empty and no existing component needs updating
  • flushThinkingToTranscript: finalize any orphaned ThinkingComponent even when thinkingDraft is empty
  • Added tests for both scenarios

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

When a provider emits an empty thinking delta (e.g. Anthropic
signature_delta -> think: ""), a ThinkingComponent was created with a
running spinner but thinkingDraft stayed empty. Subsequent calls to
flushThinkingToTranscript guarded on thinkingDraft.length and
returned early without calling onThinkingEnd(), leaking the spinner
past the turn end.

Two fixes:
- onThinkingUpdate: skip component creation for empty text when no
  existing component needs updating (source prevention).
- flushThinkingToTranscript: finalize any orphaned component even
  when thinkingDraft is empty (defensive cleanup).
Copilot AI review requested due to automatic review settings May 27, 2026 03:49
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 27, 2026

🦋 Changeset detected

Latest commit: 88894bd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds safeguards in the TUI thinking-stream rendering to prevent “thinking” spinners/components from leaking past turn end (notably when providers emit empty thinking deltas), and adds regression tests for these scenarios.

Changes:

  • Added tests covering empty thinking deltas and orphaned thinking component finalization on turn end.
  • Prevent creation of a ThinkingComponent when the streamed thinking text is empty and no component exists.
  • Ensure any existing active ThinkingComponent is finalized on flush even when thinkingDraft is empty.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/kimi-code/test/tui/kimi-tui-message-flow.test.ts Adds regression tests for empty thinking deltas and for cleaning up orphaned thinking components at turn end.
apps/kimi-code/src/tui/kimi-tui.ts Implements guards to avoid creating empty thinking components and to finalize leaked components when flushing thinking to transcript.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/kimi-code/src/tui/kimi-tui.ts Outdated
Comment on lines 1907 to 1918
private flushThinkingToTranscript(nextMode: LivePaneState['mode'] = 'idle'): void {
this.flushStreamingUiUpdatesNow();
if (this.state.thinkingDraft.length === 0) {
// A live ThinkingComponent may still exist with a running spinner
// (e.g. created by an empty thinking delta). Finalize it here so
// the spinner does not leak past the thinking phase.
if (this.state.activeThinkingComponent !== undefined) {
this.onThinkingEnd();
}
this.patchLivePane({ mode: nextMode });
return;
}

// flushThinkingToTranscript must finalize the component even when
// thinkingDraft is empty, so the spinner does not outlive the turn.
expect(driver.state.activeThinkingComponent).toBeUndefined();
Copy link
Copy Markdown
Collaborator

@liruifengv liruifengv left a comment

Choose a reason for hiding this comment

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

LGTM

@liruifengv liruifengv merged commit 2e8c417 into MoonshotAI:main May 27, 2026
4 checks passed
@github-actions github-actions Bot mentioned this pull request May 27, 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.

Bug: thinking spinner leaks past turn end on empty thinking deltas

3 participants