Skip to content

ui: show interrupted state for tool calls that never returned - #31314

Merged
ggerganov merged 2 commits into
masterfrom
allozaur/feat/interrupted-tool-call-indicator
Nov 2, 2026
Merged

ggerganov merged 2 commits into
masterfrom
allozaur/feat/interrupted-tool-call-indicator

Conversation

@allozaur

@allozaur allozaur commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Overview

A tool call that was stopped before it returned now shows as interrupted, instead of spinning on "Executing command..." for good.

Demo

Demo

Recorded at 39d6e3f; click the preview for the full video.

Additional information

Calls skipped by a steering message are unchanged: they already get a "Tool execution was interrupted by a new user message." result, shown as plain output.

Cause

The chat-level isStreaming flag was passed to every assistant message, so an older message treated its unresolved call as still pending while a newer reply streamed. Separately, exec_shell_command creates its result message before running, so stopping it mid-run left an empty result that rendered as a bare title with no status.

What changed
  • ChatMessageAgenticContent only treats the last assistant message as streaming.
  • ToolCallBlock marks a finished call without a result as interrupted, shown next to the existing failed / incomplete labels. exec_shell_command opts in to treating an empty result the same way.
  • The exec_shell_command block shows a stop icon with "Command interrupted before it returned output" instead of the spinner.
Testing
  • tests/client/exec-shell-command-streaming.svelte.test.ts covers a call left without a result after streaming stops, a command stopped before any output, and an earlier message while a newer reply streams.
  • Tests that render a live message now pass isLastAssistantMessage: true.
  • Checked in the browser against a local model: a sleep 40 command stopped mid-run shows the interrupted label.
Demo script
test.describe('pr-31314', () => {
	test('demo', async ({ demo, page }) => {
		test.setTimeout(240_000);

		const input = page.getByRole('textbox', { name: 'Type a message...' });
		const stop = page.getByRole('button', { exact: true, name: 'Stop' });
		const allow = page.getByRole('button', { name: 'Allow once' });
		const call = page.locator('[data-slot="collapsible-trigger"]').filter({ hasText: 'sleep 40' });

		await demo.type(
			input,
			'Run this exact shell command with the exec tool and nothing else: sleep 40 && echo done',
			300
		);
		await demo.press('Enter', 300);

		if (await allow.waitFor({ timeout: 120_000 }).then(() => true, () => false)) {
			await demo.click(allow, 600);
		}

		await expect(page.getByText('Executing command...').first()).toBeVisible({ timeout: 60_000 });
		await demo.caption('A long-running command is executing', 2000);

		await demo.click(stop, 800);
		await expect(call.getByText('interrupted')).toBeVisible({ timeout: 10_000 });
		await demo.caption('Stopping it marks the call as interrupted', 2400);

		await demo.click(call, 800);
		await expect(page.getByText('Command interrupted before it returned output')).toBeVisible();
		await demo.caption('Instead of a spinner that never ends', 2400);

		await demo.type(input, 'Never mind, just say hi', 300);
		await demo.press('Enter', 300);
		await expect(stop).toBeVisible({ timeout: 60_000 });
		await expect(call.getByText('interrupted')).toBeVisible();
		await demo.caption('It stays interrupted while the next reply streams', 2600);
	});
});

Requirements

The chat-level streaming flag was applied to every assistant message, so a
tool call left without a result in an earlier message kept spinning while a
newer reply streamed. Scope streaming to the last assistant message and mark
finished calls without a result as interrupted.

Assisted-by: Claude (Opus 5.5 Medium)
exec_shell_command creates its result message before running, so stopping
it mid-run leaves an empty result instead of none.

Assisted-by: Claude (Opus 5.5 Medium)
@allozaur allozaur added the video demo PR features a video demo recorded with the UI demo pipeline label Oct 29, 2026
@allozaur
allozaur marked this pull request as ready for review October 29, 2026 12:05
@allozaur
allozaur requested a review from a team as a code owner October 29, 2026 12:05
Copilot AI balanced review requested due to automatic review settings October 29, 2026 12:05

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@allozaur allozaur added the merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. label Oct 29, 2026
@ggerganov
ggerganov merged commit 64be95b into master Nov 2, 2026
8 checks passed
edwardyoon pushed a commit to edwardyoon/focus-llama that referenced this pull request Nov 6, 2026
…rg#31314)

* ui: show interrupted state for tool calls that never returned

The chat-level streaming flag was applied to every assistant message, so a
tool call left without a result in an earlier message kept spinning while a
newer reply streamed. Scope streaming to the last assistant message and mark
finished calls without a result as interrupted.

Assisted-by: Claude (Opus 5.5 Medium)

* ui: mark a shell command stopped before any output as interrupted

exec_shell_command creates its result message before running, so stopping
it mid-run leaves an empty result instead of none.

Assisted-by: Claude (Opus 5.5 Medium)
(cherry picked from commit 64be95b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge ready A maintainer can use this label to indicate that they consider the changes final and ready to merge. server/ui video demo PR features a video demo recorded with the UI demo pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants