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 -1
View File
@@ -2876,7 +2876,8 @@ func (q *sqlQuerier) AcquireChat(ctx context.Context, arg AcquireChatParams) (Ch
}
const archiveChatByID = `-- name: ArchiveChatByID :exec
UPDATE chats SET archived = true, updated_at = NOW() WHERE id = $1::uuid
UPDATE chats SET archived = true, updated_at = NOW()
WHERE id = $1 OR root_chat_id = $1
`
func (q *sqlQuerier) ArchiveChatByID(ctx context.Context, id uuid.UUID) error {
+2 -1
View File
@@ -1,5 +1,6 @@
-- name: ArchiveChatByID :exec
UPDATE chats SET archived = true, updated_at = NOW() WHERE id = @id::uuid;
UPDATE chats SET archived = true, updated_at = NOW()
WHERE id = @id OR root_chat_id = @id;
-- name: UnarchiveChatByID :exec
UPDATE chats SET archived = false, updated_at = NOW() WHERE id = @id::uuid;