[BUG] Desktop app: plugin Spinner tree (ui.render) is replaced by the default row once the row names a step

Status Open
Reported on v2.1.286
Maintainer reply None cached
Activity 0 comments · opened Oct 4, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [ ] I am using the latest version of Claude Code (the desktop app bundles 2.1.286; the CLI is at 2.1.289, but the app has not shipped it yet)

What's Wrong?

A plugin hooks on('ui.render', { component: 'Spinner' }, …) and returns its own tree on the desktop surface (a row holding an Svg and the next(e) result). The tree validates, and claude plugin test mounts it on desktop without refusal.

In the Claude desktop app (Code tab):

  • While the spinner row's word is Working, the plugin's tree is drawn.
  • As soon as the row names a step (Running a command, or a tool call's description), the app draws its default spinner row instead, although it keeps raising ui.render for the Spinner and the plugin keeps returning its tree.
  • The plugin's tree comes back briefly whenever the row returns to Working between steps.

No ui.render (Spinner) refused line appears in the transcript.

What Should Happen?

The Spinner contract in the plugin types says that on the desktop word is "what the row says its step is doing (Creating notes.md), or Working while the row shows no words". A plugin's tree should be drawn in both cases. If step rows are not meant to be replaceable, the types and docs should say so (and the engine could stop raising ui.render for them).

Error Messages/Logs

Each line is one ui.render Spinner dispatch, logged from inside the hook:
time, surface, requestId, e.props.mode, what the hook returned, hook duration, e.props.word

13:35:03 desktop main requesting svg 3ms 'Working'
13:35:05 desktop main responding svg 1ms 'Working'
13:35:05 desktop main tool-use   svg 1ms 'Running a command'
13:35:07 desktop main tool-use   svg 1ms 'Note engine start time and wait'
13:35:13 desktop main requesting svg 0ms 'Working'
13:35:14 desktop main tool-use   svg 2ms 'Running a command'
13:35:16 desktop main tool-use   svg 1ms 'Reading the spinner render trace'

All 15 dispatches in a 13-second turn returned the plugin's tree; none errored; the slowest took 23ms.
On screen the plugin's tree showed only around the 'Working' entries.

Steps to Reproduce

  1. Write a hooks module with:

``ts
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
const { Box, Text } = $.ui.resolve(e)
const theirs = await next(e)
return Box({ flexDirection: 'row', columnGap: 1, children: [Text({ color: '#d97757', children: 'PLUGIN' }), theirs] })
})
``

  1. Load it in the desktop app (for example CLAUDE_CODE_PLUGIN_DIRS=<folder>) and restart the app.
  2. Send a prompt that makes Claude run a few tool calls.
  3. PLUGIN shows while the row says Working, then disappears once the row names the running step, and reappears briefly between steps.

Claude Model

Opus

Is this a regression?

I don't know

Claude Code Version

2.1.286 (bundled with the Claude desktop app)

Platform

Anthropic API (Claude subscription)

Operating System

macOS

Terminal/Shell

Claude desktop app (Code tab)

View original on GitHub ↗