Skip to content

fix(copilot): stop async chat auto-titling from clobbering an explicit rename - #7414

Merged
waleedlatif1 merged 1 commit into
stagingfrom
fix/copilot-title-no-clobber
Sep 2, 2026
Merged

fix(copilot): stop async chat auto-titling from clobbering an explicit rename#7414
waleedlatif1 merged 1 commit into
stagingfrom
fix/copilot-title-no-clobber

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Chat title generation is fired at turn start and resolves asynchronously, but its write was unconditional:

await db.update(copilotChats).set({ title }).where(eq(copilotChats.id, chatId))

The guard that decides whether to generate a title (if (!chatId || currentChat?.title || !isNewChat) return) evaluates a snapshot taken at request start. The write then happens much later with no re-check — a classic check-then-write (TOCTOU) race. If a user renames the chat while the title is being generated, the generated title lands afterward and silently overwrites the rename.

This is reachable in normal use: rename a chat shortly after sending the first message and the rename can disappear.

Fix

Make the write conditional on the title still being unset, so a generated title can only ever fill an empty slot:

.where(and(eq(copilotChats.id, chatId), isNull(copilotChats.title)))

copilotChats.title is nullable with no default, so isNull is exactly the "no title yet" state. The update is atomic, so the explicit rename deterministically wins. When the write loses the race (0 rows), the follow-up title notification is skipped as well — we should not announce a title the row no longer holds.

Both auto-title call sites are fixed, since they had the identical unconditional write:

  • lib/copilot/request/lifecycle/start.ts — the interactive turn path
  • lib/mothership/inbox/executor.ts — the inbox task path

Why not "eliminate the race" instead

The concurrency is inherent — an async background title generator runs alongside user actions, and awaiting it would block the turn on a title LLM call. The race cannot be removed, only made well-defined; a conditional (compare-and-set) update is the standard way to do that, and it makes the outcome deterministic: an explicit title always wins, a generated one only fills a gap.

Testing

tsc --noEmit and biome clean. Existing unit tests for both changed paths pass (start.test.ts, executor.test.ts — 13 tests). This also removes the root cause of a flaky integration check that asserted a forked chat inherits its parent's renamed title.

Type of change

  • Bug fix (non-breaking)

…t rename

Chat title generation is fired at turn start and resolves asynchronously, but
its write was unconditional (`WHERE id = chatId`), re-checking nothing. The
guard that decides whether to generate reads a snapshot taken at request start,
so a title generated from that snapshot could land long after the user renamed
the chat and overwrite the rename — a classic check-then-write race. A user who
renames a chat shortly after their first message could silently lose the rename.

Make the write conditional on the title still being unset, so the generated
title fills an empty slot and can never overwrite an explicit one, and skip the
follow-up notification when the write lost the race (so we never announce a
title the row no longer holds).

Both auto-title call sites are fixed: the interactive turn path
(lib/copilot/request/lifecycle/start.ts) and the inbox task path
(lib/mothership/inbox/executor.ts), which had the identical unconditional write.
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Sep 2, 2026 8:45pm UTC

Request Review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR prevents asynchronous auto-title generation from overwriting an explicit chat rename by making both title writes conditional on the title remaining null. It also suppresses title notifications when the conditional update loses the race.

  • Adds atomic null-title checks to interactive-turn and inbox-task title generation.
  • Uses returned affected rows to determine whether a title notification should be published.

Confidence Score: 5/5

The PR appears safe to merge, with both asynchronous title paths correctly preserving explicit renames.

The conditional PostgreSQL updates atomically write only to null titles, and the returned-row checks prevent publishing a generated title when a concurrent rename has already won.

Important Files Changed

Filename Overview
apps/sim/lib/copilot/request/lifecycle/start.ts Makes interactive auto-title persistence conditional and publishes the generated title only when the database update succeeds.
apps/sim/lib/mothership/inbox/executor.ts Applies the same atomic title-write and notification behavior to inbox task execution.

Sequence Diagram

sequenceDiagram
  participant Generator as Async title generator
  participant DB as Chat database
  participant User as User rename
  participant Events as Title events
  Generator->>Generator: Begin generating title
  User->>DB: Set explicit title
  Generator->>DB: Update title where title IS NULL
  DB-->>Generator: No affected rows
  Generator-->>Events: Skip generated-title notification
Loading

Reviews (1): Last reviewed commit: "fix(copilot): stop async chat auto-titli..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit ff47aae into staging Sep 2, 2026
29 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/copilot-title-no-clobber branch September 2, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant