#15118: Fix webchat ghost bubble when model replies with NO_REPLY
app: web-ui
size: S
Cluster:
Webchat NO_REPLY Handling
## Summary
Fixes #15060
When the model returns `NO_REPLY` during streaming, the webchat was rendering an empty bubble artifact. This PR detects `NO_REPLY` text in the message rendering logic and prevents the ghost bubble from appearing.
## Changes
- Added `isSilentReplyText()` helper function to detect NO_REPLY messages in the UI
- Modified `renderGroupedMessage()` to skip rendering when message text is NO_REPLY
- Exported `renderGroupedMessage()` to enable unit testing
- Added comprehensive test suite for NO_REPLY scenarios (7 tests)
## Test plan
- ✅ All 7 new NO_REPLY tests pass
- ✅ All existing UI tests pass (199/204 total, 5 pre-existing failures unrelated to this change)
- ✅ `pnpm build` succeeds
- ✅ `pnpm check` passes (formatting, type-checking, linting)
## Technical details
The fix works by checking the extracted text before rendering and returning `nothing` from Lit when the text matches `NO_REPLY`. This prevents the bubble from being created in the DOM.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR prevents “ghost” webchat bubbles by treating `NO_REPLY` assistant outputs as silent and returning Lit’s `nothing` from `renderGroupedMessage()`.
Changes are localized to `ui/src/ui/chat/grouped-render.ts` (new `isSilentReplyText()` and exporting `renderGroupedMessage()` for testing) plus a new Vitest suite covering several `NO_REPLY`/empty-text scenarios.
One correctness concern: the UI `isSilentReplyText()` implementation diverges from the server’s boundary-based `isSilentReplyText` in `src/auto-reply/tokens.ts`, which can cause the UI to suppress messages the server would not consider silent and the new tests currently lock in that divergent behavior.
<h3>Confidence Score: 3/5</h3>
- This PR is close to mergeable but has a semantic mismatch that can hide real chat content.
- The rendering change is small and targeted, but the new UI-only `isSilentReplyText()` does not match the server’s boundary-based definition, so it can suppress non-silent outputs; the accompanying tests currently encode that behavior.
- ui/src/ui/chat/grouped-render.ts and ui/src/ui/chat/grouped-render.test.ts
<sub>Last reviewed commit: d950f43</sub>
<!-- greptile_other_comments_section -->
<sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#8334: fix(webchat): Filter NO_REPLY messages from chat history
by vishaltandale00 · 2026-02-03
81.0%
#8493: fix(tui): filter NO_REPLY token from chat display
by gavinbmoore · 2026-02-04
80.5%
#23761: fix: suppress partial NO_REPLY tokens at lifecycle boundary
by kami-saia · 2026-02-22
79.7%
#16361: Gateway: suppress NO_REPLY in webchat
by shadril238 · 2026-02-14
78.6%
#3721: fix(ui): webchat not displaying chat responses
by maxmaxrouge-rgb · 2026-01-29
77.8%
#8742: fix(webchat): hide internal system messages from UI (#7440)
by revenuestack · 2026-02-04
77.8%
#19916: fix: strict silent-reply detection to prevent false positives with ...
by hayoial · 2026-02-18
77.7%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
77.4%
#4495: Fix: emit final assistant event when reply tags hide stream
by ukeate · 2026-01-30
77.3%
#21462: fix(agents): hold back partial NO_REPLY token in pi-embedded streaming
by algal · 2026-02-20
77.3%