mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: re-read chat before publishing status event to preserve AI title (#22402)
## Problem Chat titles revert to the fallback truncated title after briefly showing the AI-generated title. Even reloading the page doesn't help — the correct title flashes then gets overwritten. ## Root Cause Single bug, two symptoms. In `processChat` (`coderd/chatd/chatd.go`), the `chat` variable is passed by value. The flow: 1. `processChat(ctx, chat)` receives `chat` with the initial fallback title (truncated first message). 2. Inside `runChat`, `maybeGenerateChatTitle` generates an AI title, writes it to the DB via `UpdateChatByID`, and publishes a `title_change` event. **The DB has the correct title.** The client briefly displays it. 3. `runChat` returns. The **deferred cleanup** in `processChat` publishes `publishChatPubsubEvent(chat, StatusChange)` — but `chat` here is the original value copy that still has the **old fallback title**. 4. The frontend receives the `status_change` SSE event and **unconditionally applies `title` from every event kind** (see `AgentsPage.tsx` line ~305: `title: updatedChat.title`). This overwrites the correct AI title with the stale fallback. **Why reload doesn't help:** If the chat is still processing when the page reloads, `listChats` loads the correct title from the DB, but then the deferred `status_change` event arrives moments later and clobbers it. The title was always in the DB — it was the pubsub event that kept overwriting it. ## Fix Re-read the chat from the database in the deferred cleanup before publishing the final `status_change` event, so it carries the current (AI-generated) title.
This commit is contained in:
+14
-1
@@ -1689,6 +1689,16 @@ func (p *Server) processChat(ctx context.Context, chat database.Chat) {
|
||||
}
|
||||
|
||||
p.publishStatus(chat.ID, status, uuid.NullUUID{})
|
||||
// Re-read the chat from the database to pick up any title
|
||||
// changes made during processing (e.g. AI-generated titles
|
||||
// from maybeGenerateChatTitle). The local `chat` variable
|
||||
// is a value copy and won't reflect updates made in runChat.
|
||||
if freshChat, readErr := p.db.GetChatByID(ctx, chat.ID); readErr == nil {
|
||||
chat = freshChat
|
||||
} else {
|
||||
logger.Warn(ctx, "failed to re-read chat for status event",
|
||||
slog.F("chat_id", chat.ID), slog.Error(readErr))
|
||||
}
|
||||
chat.Status = status
|
||||
p.publishChatPubsubEvent(chat, coderdpubsub.ChatEventKindStatusChange)
|
||||
}()
|
||||
@@ -1729,7 +1739,10 @@ func (p *Server) runChat(
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get chat messages: %w", err)
|
||||
}
|
||||
p.maybeGenerateChatTitle(ctx, chat, messages, model, logger)
|
||||
// Fire title generation asynchronously so it doesn't block the
|
||||
// chat response. It uses a detached context so it can finish
|
||||
// even after the chat processing context is canceled.
|
||||
go p.maybeGenerateChatTitle(context.WithoutCancel(ctx), chat, messages, model, logger)
|
||||
|
||||
prompt, err := chatprompt.ConvertMessages(messages)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user