From 77c3883cee0f82191d440b5f8bab0e3d1b5866dc Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Wed, 13 May 2026 22:58:53 +0000 Subject: [PATCH 1/3] fix(code): respect manually-renamed task titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The auto-title generator only skipped the LLM-driven rename on subsequent generations, so the very first auto-title (after the 1st prompt) would still overwrite a title the user had already set manually. Drop the first-generation exemption and only skip the title update — the conversation summary still updates either way. Generated-By: PostHog Code Task-Id: fafd9cbe-c2b3-47d1-879c-6a316008634a --- .../hooks/useChatTitleGenerator.test.ts | 29 +++++++++++-- .../sessions/hooks/useChatTitleGenerator.ts | 42 +++++++++---------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts index 061244159..5866ab71e 100644 --- a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts +++ b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts @@ -103,7 +103,7 @@ describe("useChatTitleGenerator", () => { }); }); - it("allows first generation even when title_manually_set", async () => { + it("skips first generation when title_manually_set", async () => { mockGetCachedTask.mockReturnValue({ id: TASK_ID, title_manually_set: true, @@ -117,10 +117,31 @@ describe("useChatTitleGenerator", () => { renderHook(() => useChatTitleGenerator(TASK_ID)); await waitFor(() => { - expect(mockUpdateTask).toHaveBeenCalledWith(TASK_ID, { - title: "Auto title", - }); + expect(mockGenerateTitle).toHaveBeenCalled(); }); + expect(mockUpdateTask).not.toHaveBeenCalled(); + }); + + it("still updates summary when title is skipped due to manual rename", async () => { + mockGetCachedTask.mockReturnValue({ + id: TASK_ID, + title_manually_set: true, + }); + mockGenerateTitle.mockResolvedValue({ + title: "Auto title", + summary: "User wants to fix auth", + }); + mockPrompts.value = ["fix auth"]; + + renderHook(() => useChatTitleGenerator(TASK_ID)); + + await waitFor(() => { + expect(mockSessionStoreSetters.updateSession).toHaveBeenCalledWith( + "run-1", + { conversationSummary: "User wants to fix auth" }, + ); + }); + expect(mockUpdateTask).not.toHaveBeenCalled(); }); it("calls enrichDescriptionWithFileContent before generating", async () => { diff --git a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts index 3cab24164..42300f05d 100644 --- a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts +++ b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts @@ -74,30 +74,26 @@ export function useChatTitleGenerator(taskId: string): void { if (result) { const { title, summary } = result; if (title) { - const isFirstGeneration = lastGeneratedAtCount.current === 0; - if ( - !isFirstGeneration && - getCachedTask(taskId)?.title_manually_set - ) { + if (getCachedTask(taskId)?.title_manually_set) { log.debug("Skipping auto-title, user renamed task", { taskId }); - return; - } - const client = await getAuthenticatedClient(); - if (client) { - await client.updateTask(taskId, { title }); - queryClient.setQueriesData( - { queryKey: ["tasks", "list"] }, - (old) => - old?.map((task) => - task.id === taskId ? { ...task, title } : task, - ), - ); - getSessionService().updateSessionTaskTitle(taskId, title); - log.debug("Updated task title from conversation", { - taskId, - title, - promptCount, - }); + } else { + const client = await getAuthenticatedClient(); + if (client) { + await client.updateTask(taskId, { title }); + queryClient.setQueriesData( + { queryKey: ["tasks", "list"] }, + (old) => + old?.map((task) => + task.id === taskId ? { ...task, title } : task, + ), + ); + getSessionService().updateSessionTaskTitle(taskId, title); + log.debug("Updated task title from conversation", { + taskId, + title, + promptCount, + }); + } } } From 73a2306058f79e392b65e6bcc8897f94eb81a3a9 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 14 May 2026 00:16:05 +0000 Subject: [PATCH 2/3] refactor(code): tighten auto-title guard per review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Drop user-derived title/summary from success-path debug logs so the LLM output isn't leaked if renderer debug logs are persisted. - Flatten the nested title/manual-rename conditional into a single if/else-if pair using a `titleLocked` local — reads as intent. - Rename misleading test ("skips first generation…" → "skips title update…") to match the post-fix behaviour. Generated-By: PostHog Code Task-Id: fafd9cbe-c2b3-47d1-879c-6a316008634a --- .../hooks/useChatTitleGenerator.test.ts | 2 +- .../sessions/hooks/useChatTitleGenerator.ts | 42 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts index 5866ab71e..09bea79d5 100644 --- a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts +++ b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts @@ -103,7 +103,7 @@ describe("useChatTitleGenerator", () => { }); }); - it("skips first generation when title_manually_set", async () => { + it("skips title update when title_manually_set", async () => { mockGetCachedTask.mockReturnValue({ id: TASK_ID, title_manually_set: true, diff --git a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts index 42300f05d..048436046 100644 --- a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts +++ b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.ts @@ -73,27 +73,26 @@ export function useChatTitleGenerator(taskId: string): void { const result = await generateTitleAndSummary(content); if (result) { const { title, summary } = result; - if (title) { - if (getCachedTask(taskId)?.title_manually_set) { - log.debug("Skipping auto-title, user renamed task", { taskId }); - } else { - const client = await getAuthenticatedClient(); - if (client) { - await client.updateTask(taskId, { title }); - queryClient.setQueriesData( - { queryKey: ["tasks", "list"] }, - (old) => - old?.map((task) => - task.id === taskId ? { ...task, title } : task, - ), - ); - getSessionService().updateSessionTaskTitle(taskId, title); - log.debug("Updated task title from conversation", { - taskId, - title, - promptCount, - }); - } + const titleLocked = !!getCachedTask(taskId)?.title_manually_set; + + if (title && titleLocked) { + log.debug("Skipping auto-title, user renamed task", { taskId }); + } else if (title) { + const client = await getAuthenticatedClient(); + if (client) { + await client.updateTask(taskId, { title }); + queryClient.setQueriesData( + { queryKey: ["tasks", "list"] }, + (old) => + old?.map((task) => + task.id === taskId ? { ...task, title } : task, + ), + ); + getSessionService().updateSessionTaskTitle(taskId, title); + log.debug("Updated task title from conversation", { + taskId, + promptCount, + }); } } @@ -104,7 +103,6 @@ export function useChatTitleGenerator(taskId: string): void { log.debug("Updated task summary from conversation", { taskId, - summary, promptCount, }); } From 7d4f3a4a95e8e980f52c4496e9967155af9a047d Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Thu, 14 May 2026 00:32:59 +0000 Subject: [PATCH 3/3] test(code): parameterise title_manually_set skip cases Per Greptile review feedback, fold the two summary-variant tests into a single it.each so the no-summary and with-summary cases share setup. Generated-By: PostHog Code Task-Id: fafd9cbe-c2b3-47d1-879c-6a316008634a --- .../hooks/useChatTitleGenerator.test.ts | 73 +++++++++---------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts index 09bea79d5..ee11f6323 100644 --- a/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts +++ b/apps/code/src/renderer/features/sessions/hooks/useChatTitleGenerator.test.ts @@ -103,46 +103,45 @@ describe("useChatTitleGenerator", () => { }); }); - it("skips title update when title_manually_set", async () => { - mockGetCachedTask.mockReturnValue({ - id: TASK_ID, - title_manually_set: true, - }); - mockGenerateTitle.mockResolvedValue({ - title: "Auto title", - summary: "", - }); - mockPrompts.value = ["some prompt"]; - - renderHook(() => useChatTitleGenerator(TASK_ID)); - - await waitFor(() => { - expect(mockGenerateTitle).toHaveBeenCalled(); - }); - expect(mockUpdateTask).not.toHaveBeenCalled(); - }); - - it("still updates summary when title is skipped due to manual rename", async () => { - mockGetCachedTask.mockReturnValue({ - id: TASK_ID, - title_manually_set: true, - }); - mockGenerateTitle.mockResolvedValue({ - title: "Auto title", + it.each([ + { name: "no summary", summary: "", expectsSummaryUpdate: false }, + { + name: "with summary", summary: "User wants to fix auth", - }); - mockPrompts.value = ["fix auth"]; + expectsSummaryUpdate: true, + }, + ])( + "skips title update when title_manually_set ($name)", + async ({ summary, expectsSummaryUpdate }) => { + mockGetCachedTask.mockReturnValue({ + id: TASK_ID, + title_manually_set: true, + }); + mockGenerateTitle.mockResolvedValue({ + title: "Auto title", + summary, + }); + mockPrompts.value = ["fix auth"]; - renderHook(() => useChatTitleGenerator(TASK_ID)); + renderHook(() => useChatTitleGenerator(TASK_ID)); - await waitFor(() => { - expect(mockSessionStoreSetters.updateSession).toHaveBeenCalledWith( - "run-1", - { conversationSummary: "User wants to fix auth" }, - ); - }); - expect(mockUpdateTask).not.toHaveBeenCalled(); - }); + await waitFor(() => { + expect(mockGenerateTitle).toHaveBeenCalled(); + }); + expect(mockUpdateTask).not.toHaveBeenCalled(); + + if (expectsSummaryUpdate) { + await waitFor(() => { + expect(mockSessionStoreSetters.updateSession).toHaveBeenCalledWith( + "run-1", + { conversationSummary: summary }, + ); + }); + } else { + expect(mockSessionStoreSetters.updateSession).not.toHaveBeenCalled(); + } + }, + ); it("calls enrichDescriptionWithFileContent before generating", async () => { mockEnrichDescription.mockResolvedValue("enriched content");