Skip to content

fix: agent tool status not stopping on abort#13111

Merged
kangfenmao merged 3 commits intomainfrom
DeJeune/fix-agent-tool-stop
Mar 9, 2026
Merged

fix: agent tool status not stopping on abort#13111
kangfenmao merged 3 commits intomainfrom
DeJeune/fix-agent-tool-stop

Conversation

@DeJeune
Copy link
Copy Markdown
Collaborator

@DeJeune DeJeune commented Feb 28, 2026

What this PR does

Before this PR:

When clicking the stop button during an agent session, tool status indicators (loading spinners, "Invoking"/"Streaming" labels) continued showing active states. The issue persisted even after refreshing the renderer process.

After this PR:

All in-progress tool blocks properly transition to cancelled/error state when the user clicks stop, and the state is persisted to IndexedDB so it survives renderer refreshes.

Why we need it and why it was done in this way

The UI for tool blocks reads status from rawMcpToolResponse.status (e.g., 'pending', 'streaming', 'invoking'), not from MessageBlockStatus. When abort fires, baseCallbacks.onError only updated MessageBlockStatus for blocks in STREAMING state but never touched rawMcpToolResponse.status inside tool block metadata. Additionally, tool blocks typically have MessageBlockStatus.PENDING (not STREAMING), so they were missed entirely by the existing abort cleanup logic. The Redux updates were also not persisted to IndexedDB.

The fix:

  1. Updates rawMcpToolResponse.status to 'cancelled'/'error' for all in-progress tool blocks on abort
  2. Collects all updated blocks and passes them to saveUpdatesToDB for IndexedDB persistence

The following tradeoffs were made:

None significant — the fix is minimal and follows the existing pattern.

The following alternatives were considered:

  • Updating tool status in the abort handler at the inputbar level — rejected because baseCallbacks.onError already handles all abort cleanup centrally.

Breaking changes

None.

Special notes for your reviewer

The dual status system (MessageBlockStatus vs MCPToolResponseStatus) is the root cause. The UI components (ToolStatusIndicator, ToolBlockGroup, MessageAgentTools) all read from rawMcpToolResponse.status for tool display, while the existing abort logic only updated MessageBlockStatus.

Checklist

  • PR: The PR description is expressive enough and will help future contributors
  • Code: Write code that humans can understand and Keep it simple
  • Refactor: You have left the code cleaner than you found it (Boy Scout Rule)
  • Upgrade: Impact of this change on upgrade flows was considered and addressed if required
  • Documentation: A user-guide update was considered and is present (link) or not required.
  • Self-review: I have reviewed my own code before requesting review from others

Release note

fix: agent tool status not stopping on abort

DeJeune and others added 2 commits February 28, 2026 14:51
When clicking stop during agent session, tool status indicators continued
showing active states because the UI reads from rawMcpToolResponse.status
which was never updated on abort. Now sets it to 'cancelled' (abort) or
'error' (real error) for all in-progress tool blocks.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
When clicking stop during agent session, tool status indicators continued
showing active states because:
1. The UI reads from rawMcpToolResponse.status which was never updated
2. Block updates were not persisted to IndexedDB

Now updates rawMcpToolResponse.status to 'cancelled'/'error' for all
in-progress tool blocks and persists the changes via saveUpdatesToDB.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@DeJeune DeJeune requested a review from EurFelux February 28, 2026 07:03
Skip TOOL type blocks in the STREAMING branch since they are handled
in the dedicated tool block branch below, avoiding a wasted dispatch
that gets immediately overwritten.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@DeJeune DeJeune added this to the v1.7.22 milestone Feb 28, 2026
Copy link
Copy Markdown
Collaborator

@GeorgeDong32 GeorgeDong32 left a comment

Choose a reason for hiding this comment

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

Note

This issue/comment/review was translated by Claude.

LGTM! Code review passed, fix solution is precise, code quality is excellent. Recommended to merge.


Original Content

LGTM! 代码审查通过,修复方案精准,代码质量优秀。建议合并。

@DeJeune DeJeune requested review from kangfenmao and zhibisora March 5, 2026 05:52
Copy link
Copy Markdown
Collaborator

@EurFelux EurFelux left a comment

Choose a reason for hiding this comment

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

LGTM! Clean and precise fix for the dual-status system desync on abort.

Summary:

  • Root cause correctly identified: UI reads rawMcpToolResponse.status but abort only updated MessageBlockStatus
  • Tool blocks (typically in PENDING state, not STREAMING) were missed by the existing cleanup logic
  • Fix properly updates both status systems and persists to IndexedDB

The code is minimal, follows existing patterns, and handles edge cases well. One minor non-blocking suggestion left inline about using a positive set check for readability.

Comment on lines +170 to 198
if (block.type === MessageBlockType.TOOL) {
const toolBlock = block as ToolMessageBlock
const toolResponse = toolBlock.metadata?.rawMcpToolResponse
const toolStatus = toolResponse?.status
if (
toolResponse &&
toolStatus &&
toolStatus !== 'done' &&
toolStatus !== 'error' &&
toolStatus !== 'cancelled'
) {
dispatch(
updateOneBlock({
id: block.id,
changes: {
status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR,
metadata: {
...toolBlock.metadata,
rawMcpToolResponse: {
...toolResponse,
status: isErrorTypeAbort ? 'cancelled' : 'error'
}
}
}
})
)
updatedBlockIds.push(block.id)
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good fix! The dual-status system (MessageBlockStatus vs rawMcpToolResponse.status) mismatch was the root cause, and this correctly addresses it.

Nit (non-blocking): This branch intentionally does NOT skip possibleBlockId (unlike the STREAMING branch above at line 148), which means if possibleBlockId happens to be a TOOL block, its MessageBlockStatus gets set twice — once via smartBlockUpdate (line 123) and once here. This is harmless (same value both times) and actually desirable since only this branch updates rawMcpToolResponse.status. Just noting for clarity.

Minor suggestion: The negated status checks could be slightly more readable as a positive set check:

const IN_PROGRESS_TOOL_STATUSES = new Set(['pending', 'streaming', 'invoking'])
if (toolResponse && toolStatus && IN_PROGRESS_TOOL_STATUSES.has(toolStatus)) {

This makes it self-documenting and easier to extend if new statuses are added. But the current form is also perfectly clear — up to you.

Comment on lines +216 to +219
const blocksToSave = updatedBlockIds
.map((id) => getState().messageBlocks.entities[id])
.filter(Boolean) as MessageBlock[]
await saveUpdatesToDB(assistantMsgId, topicId, messageErrorUpdate, blocksToSave)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice approach — collecting updatedBlockIds then reading from updated state after synchronous dispatches is the right pattern here. This ensures the blocks passed to saveUpdatesToDB contain the final merged state (including the updated rawMcpToolResponse.status), rather than stale pre-dispatch snapshots.

Previously saveUpdatesToDB was called with [], meaning tool block status changes were lost on renderer refresh — this fixes the persistence gap.

Copy link
Copy Markdown
Collaborator

@zhibisora zhibisora left a comment

Choose a reason for hiding this comment

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

LGTM. I reviewed the abort cleanup fix and found no blocking issues. The change correctly updates tool UI state via rawMcpToolResponse.status, persists the updated blocks to IndexedDB, stays narrowly scoped, and aligns with the existing callback flow. CI is green and the fix addresses the reported refresh-persistence gap.

@kangfenmao kangfenmao merged commit 85ec71a into main Mar 9, 2026
20 checks passed
@kangfenmao kangfenmao deleted the DeJeune/fix-agent-tool-stop branch March 9, 2026 12:14
@kangfenmao kangfenmao mentioned this pull request Mar 13, 2026
6 tasks
kangfenmao added a commit that referenced this pull request Mar 13, 2026
### What this PR does

Before this PR:
- Version is 1.7.24

After this PR:
- Version is 1.7.25
- Release notes updated in `electron-builder.yml`

### Why we need it and why it was done in this way

Standard release workflow: collect commits since v1.7.24, generate
bilingual release notes, bump version.

The following tradeoffs were made:
N/A

The following alternatives were considered:
N/A

### Breaking changes

**Action Required**: The built-in filesystem MCP server now requires
manual approval for write/edit/delete operations by default.

### Special notes for your reviewer

**Included commits since v1.7.24:**

✨ New Features:
- feat(websearch): add Querit search provider (#13050)
- feat(minapp,provider): add MiniMax Agent, IMA mini apps and MiniMax
Global, Z.ai providers (#13099)
- feat(provider): add agent support filter for provider list (#11932)
- feat(agent): add custom environment variables to agent configuration
(#13357)
- feat: add GPT-5.4 support (#13293)
- feat(gemini): add thought signature persistence (#13100)

🐛 Bug Fixes:
- fix: secure built-in filesystem MCP root handling (#13294)
- fix: check underlying tool permissions for hub invoke/exec (#13282)
- fix: remove approval countdown timers and add system notifications
(#13281)
- fix: agent tool status not stopping on abort (#13111)
- fix: resolve spawn ENOENT on Windows for Code Tools (#13405)
- fix: Use default assistant for topic auto-renaming (#13387)
- fix(backup): defer auto backup during streaming response (#13307)
- fix(ui): fix Move To submenu overflow (#13399)
- fix: render xml fenced svg blocks as svg previews (#13431)
- fix: correct Gemini reasoning params (#13388)
- fix: improve Qwen 3.5 reasoning model detection (#13235)
- fix: upgrade xAI web search to Responses API (#12812)
- fix(api-server): relax chat completion validation (#13279)
- fix: install or uninstall button state issues (#13114)
- fix: improve update dialog behavior (#13363)
- fix: auto-convert reasoning_effort to reasoningEffort (#12831)

### Checklist

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: Write code that humans can understand and Keep it simple
- [x] Refactor: You have left the code cleaner than you found it (Boy
Scout Rule)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [x] Documentation: A user-guide update was considered and is present
(link) or not required.
- [x] Self-review: I have reviewed my own code before requesting review
from others

### Release note

```release-note
See release notes in electron-builder.yml
```

---------

Signed-off-by: kangfenmao <[email protected]>
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.

5 participants