From 148e56b5d9a020f6b2ce5e9a0619586b27ab7d37 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Tue, 21 Apr 2026 23:10:00 +0700 Subject: [PATCH] fix(coderd): fix TestPatchChat/Title flake by waiting for chat to settle (#24572) ## Problem `TestPatchChat/Title/Rename` and `TestPatchChat/Title/TrimsWhitespace` fail intermittently on `test-go-pg` with: ``` PATCH .../api/experimental/chats/: unexpected status code 409: Title regeneration already in progress for this chat. ``` `createChat` persists a chat with `ChatStatusPending` and signals the daemon wake loop. If the `UpdateChat` PATCH arrives before the daemon transitions the chat past `Pending`/`Running`, the handler's `acquireManualTitleLock` returns a 409. Whether the PATCH wins the race is timing-dependent under PG + `-parallel` load. Sibling subtests `PreservesUpdatedAt` and `NoOpWhenTitleUnchanged` already wait for the chat to leave `Pending`/`Running` before renaming, which is why they do not flake. ## Fix Add a `waitChatSettled` helper closure in `TestPatchChat` that polls `client.GetChat` until the chat status leaves `Pending`/`Running`. Call it in the 4 subtests that issue a valid rename immediately after `createChat`: - `Title/Rename` (originally reported flake) - `Title/TrimsWhitespace` (originally reported flake) - `Title/LengthBoundaries` (latent flake in valid-rename cases) - `Title/PublishesWatchEvent` (latent flake, goroutine silently 409s) No handler, daemon, or SDK changes. The 409 is intentional production behavior; this is a pure test-side timing fix. Refs coder/internal#1480 --- coderd/exp_chats_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/coderd/exp_chats_test.go b/coderd/exp_chats_test.go index de3b94280c..b158096a87 100644 --- a/coderd/exp_chats_test.go +++ b/coderd/exp_chats_test.go @@ -3973,6 +3973,22 @@ func TestPatchChat(t *testing.T) { require.NoError(t, err) return db2sdk.Chat(dbChat, nil, nil) } + + // waitChatSettled polls the chat until its background title-generation + // daemon has left the Pending/Running state. Without this, an immediate + // UpdateChat can hit a 409 (title regeneration in progress). + waitChatSettled := func(ctx context.Context, t *testing.T, client *codersdk.ExperimentalClient, chatID uuid.UUID) { + t.Helper() + require.Eventually(t, func() bool { + c, err := client.GetChat(ctx, chatID) + if err != nil { + return false + } + return c.Status != codersdk.ChatStatusPending && + c.Status != codersdk.ChatStatusRunning + }, testutil.WaitShort, testutil.IntervalFast) + } + t.Run("PlanMode", func(t *testing.T) { t.Parallel() @@ -4244,6 +4260,8 @@ func TestPatchChat(t *testing.T) { chat := createChat(ctx, t, client, firstUser.OrganizationID, "original title") + waitChatSettled(ctx, t, client, chat.ID) + err := client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{ Title: ptr.Ref("renamed title"), }) @@ -4263,6 +4281,8 @@ func TestPatchChat(t *testing.T) { chat := createChat(ctx, t, client, firstUser.OrganizationID, "before trim") + waitChatSettled(ctx, t, client, chat.ID) + err := client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{ Title: ptr.Ref(" padded title "), }) @@ -4360,6 +4380,8 @@ func TestPatchChat(t *testing.T) { _ = createChatModelConfig(t, client) chat := createChat(ctx, t, client, firstUser.OrganizationID, "boundary baseline") + waitChatSettled(ctx, t, client, chat.ID) + err := client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{ Title: ptr.Ref(tc.title), }) @@ -4471,6 +4493,8 @@ func TestPatchChat(t *testing.T) { chat := createChat(ctx, t, client, firstUser.OrganizationID, "announce me") + waitChatSettled(ctx, t, client, chat.ID) + conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil) require.NoError(t, err) defer conn.Close(websocket.StatusNormalClosure, "done")