fix(coderd/x/chatd): forward local status events to fix delayed-startup banner (#23650)

## Problem

The agent chat delayed-startup banner ("Response startup is taking
longer than expected") could appear even though the model was already
streaming.

The root cause is in `Subscribe()`: `message_part` events were delivered
via the fast local in-process stream, while `status` events were
delivered via PostgreSQL pubsub. Both feed into the same `select`
statement, and Go's `select` picks whichever channel is ready first —
there is no ordering guarantee between channels. So a `message_part`
could outrun the `status=running` that logically precedes it.

The frontend saw content arrive while it still thought the chat was
pending, triggering the banner.

## Fix

Also forward `status` events from the local channel, alongside
`message_part`.

Both event types already travel through the same FIFO subscriber
channel: `publishStatus()` is called before the first `message_part`, so
channel ordering guarantees the frontend sees `status=running` before
any content.

Pubsub still delivers a duplicate `status` event later; the frontend
deduplicates it (`setChatStatus` is idempotent — it early-returns when
the status hasn't changed).
This commit is contained in:
Ethan
2026-03-27 17:55:19 +11:00
committed by GitHub
parent 86c3983fc0
commit d678c6fb16
+14 -3
View File
@@ -2871,9 +2871,20 @@ func (p *Server) Subscribe(
continue
}
if hasPubsub {
// Only forward message_part events from local
// (durable events come via pubsub + cache).
if event.Type == codersdk.ChatStreamEventTypeMessagePart {
// Forward transient events from local.
// Durable events (messages, queue updates)
// come via pubsub + cache. Status is
// included alongside message_part because
// both travel through the same ordered
// channel: publishStatus is called before
// the first message_part, so FIFO delivery
// guarantees the frontend sees
// status=running before any content.
// Pubsub will deliver a duplicate status
// later; the frontend deduplicates it
// (setChatStatus is idempotent).
if event.Type == codersdk.ChatStreamEventTypeMessagePart ||
event.Type == codersdk.ChatStreamEventTypeStatus {
select {
case <-mergedCtx.Done():
return