@@ -2,7 +2,7 @@ import { copilotChats, db, mothershipInboxTask, user, workspace } from '@sim/db'
22import { createLogger } from '@sim/logger'
33import { getErrorMessage } from '@sim/utils/errors'
44import { generateId } from '@sim/utils/id'
5- import { and , eq , sql } from 'drizzle-orm'
5+ import { and , eq , isNull , sql } from 'drizzle-orm'
66import { getActivelyBannedUserIds , isEmailBlocked } from '@/lib/auth/ban'
77import { resolveBillingAttribution } from '@/lib/billing/core/billing-attribution'
88import { resolveOrCreateChat } from '@/lib/copilot/chat/lifecycle'
@@ -167,7 +167,17 @@ export async function executeInboxTask(taskId: string): Promise<void> {
167167 } )
168168 . then ( async ( title ) => {
169169 if ( title && chatId ) {
170- await db . update ( copilotChats ) . set ( { title } ) . where ( eq ( copilotChats . id , chatId ) )
170+ // Only stamp the generated title while the chat has none. This
171+ // resolves asynchronously, so a user could rename the chat in the
172+ // meantime; the `isNull` guard makes the write lose that race
173+ // instead of clobbering the explicit rename.
174+ const stamped = await db
175+ . update ( copilotChats )
176+ . set ( { title } )
177+ . where ( and ( eq ( copilotChats . id , chatId ) , isNull ( copilotChats . title ) ) )
178+ . returning ( { id : copilotChats . id } )
179+ // The rename won — do not announce a title the row no longer holds.
180+ if ( stamped . length === 0 ) return
171181 chatPubSub ?. publishStatusChanged ( {
172182 workspaceId : ws . id ,
173183 chatId,
0 commit comments