fix(chats): archive chat tree with single query instead of loop (#22496)

## Problem

When archiving an agent with subagents, the children briefly flash in
the sidebar as root-level items before disappearing. Two issues:

1. **Backend:** Archive used N+1 queries — a recursive DFS
(`archiveChatTree`, no transaction) or BFS loop (`chatd.ArchiveChat`,
N+1 queries in a tx) to walk the tree and archive each chat
individually.
2. **Frontend:** The SSE `deleted` event handler only filtered out the
parent chat from the cache. Children remained briefly, got promoted to
root-level by `buildChatTree`, then disappeared on the next re-fetch.

## Fix

**Backend:** Replace both tree-walk implementations with a single SQL
query:
```sql
UPDATE chats SET archived = true, updated_at = NOW()
WHERE id = @id OR root_chat_id = @id;
```
This leverages the existing `root_chat_id` column (already indexed) to
archive the entire tree atomically.

**Frontend:** When a `deleted` event arrives, also filter out any chats
whose `root_chat_id` matches the deleted chat, so children vanish from
the sidebar immediately with the parent.

## Changes

- `coderd/database/queries/chats.sql` — Added `ArchiveChatTreeByID`
query
- `coderd/chats.go` — Use single query, delete `archiveChatTree`
function
- `coderd/chatd/chatd.go` — Simplify `ArchiveChat` to use single query
- `coderd/database/dbauthz/dbauthz.go` — Auth wrapper for new query
- `coderd/chats_test.go` — Added `TestArchiveChat/ArchivesChildren`
subtest
- `site/src/pages/AgentsPage/AgentsPage.tsx` — Filter children in SSE
handler
- Generated files updated via `make gen`
This commit is contained in:
Kyle Carberry
2026-03-02 12:00:00 -05:00
committed by GitHub
parent 7bc454eed8
commit 0908505348
6 changed files with 72 additions and 54 deletions
+2 -32
View File
@@ -517,38 +517,8 @@ func (p *Server) ArchiveChat(ctx context.Context, chatID uuid.UUID) error {
return xerrors.Errorf("get chat: %w", err)
}
err = p.db.InTx(func(tx database.Store) error {
// Collect descendants breadth-first, then archive from leaves upward.
descendantIDs := make([]uuid.UUID, 0)
queue := []uuid.UUID{chatID}
for len(queue) > 0 {
parentID := queue[0]
queue = queue[1:]
children, err := tx.ListChildChatsByParentID(ctx, parentID)
if err != nil {
return xerrors.Errorf("list children of chat %s: %w", parentID, err)
}
for _, child := range children {
descendantIDs = append(descendantIDs, child.ID)
queue = append(queue, child.ID)
}
}
for i := len(descendantIDs) - 1; i >= 0; i-- {
if err := tx.ArchiveChatByID(ctx, descendantIDs[i]); err != nil {
return xerrors.Errorf("archive descendant chat %s: %w", descendantIDs[i], err)
}
}
if err := tx.ArchiveChatByID(ctx, chatID); err != nil {
return xerrors.Errorf("archive chat: %w", err)
}
return nil
}, nil)
if err != nil {
return err
if err := p.db.ArchiveChatByID(ctx, chatID); err != nil {
return xerrors.Errorf("archive chat: %w", err)
}
p.publishChatPubsubEvent(chat, coderdpubsub.ChatEventKindDeleted)