Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 934474d

Browse files
authored
fix: harden PR title generation
Generated-By: PostHog Code Task-Id: 40b5cfd6-d4c2-4751-b6c4-e1fb31a78bc2
1 parent c66481c commit 934474d

3 files changed

Lines changed: 68 additions & 6 deletions

File tree

apps/web/src/web-sessions-clients.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,18 @@ export const webReadFileAsBase64: ReadFileAsBase64 = (filePath: string) =>
3232

3333
export const webGithubPrTitleClient: GithubPrTitleClient = {
3434
getGithubPullRequestTitle: async ({ owner, repo, number }) => {
35-
const response = await fetch(
36-
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${number}`,
37-
{ headers: { Accept: "application/vnd.github+json" } },
38-
);
35+
let response: Response;
36+
try {
37+
response = await fetch(
38+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${number}`,
39+
{
40+
headers: { Accept: "application/vnd.github+json" },
41+
signal: AbortSignal.timeout(5_000),
42+
},
43+
);
44+
} catch {
45+
return null;
46+
}
3947
if (!response.ok) return null;
4048
const payload: unknown = await response.json();
4149
if (

packages/ui/src/features/sessions/hooks/useChatTitleGenerator.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const mockEnrichDescription = vi.hoisted(() =>
1010
const mockGenerateTitle = vi.hoisted(() => vi.fn());
1111
const mockGetQueriesData = vi.hoisted(() => vi.fn(() => [] as unknown[]));
1212
const mockIsAuthenticated = vi.hoisted(() => ({ value: true }));
13+
const mockCurrentUser = vi.hoisted(() => ({
14+
value: { id: 1 } as { id: number } | undefined,
15+
}));
1316
const mockUpdateTask = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
1417
const mockSetQueriesData = vi.hoisted(() => vi.fn());
1518
const mockSetQueryData = vi.hoisted(() => vi.fn());
@@ -48,6 +51,10 @@ vi.mock("@posthog/ui/features/auth/store", () => ({
4851
),
4952
}));
5053

54+
vi.mock("@posthog/ui/features/auth/useCurrentUser", () => ({
55+
useCurrentUser: () => ({ data: mockCurrentUser.value }),
56+
}));
57+
5158
vi.mock("@posthog/core/sessions/sessionEvents", () => ({
5259
extractUserPromptsFromEvents: () => mockPrompts.value,
5360
}));
@@ -116,6 +123,11 @@ function createTask(overrides: Partial<Task> = {}): Task {
116123
created_at: "2026-05-28T00:00:00.000Z",
117124
updated_at: "2026-05-28T00:00:00.000Z",
118125
origin_product: "user_created",
126+
created_by: {
127+
id: 1,
128+
uuid: "user-1",
129+
email: "user@example.com",
130+
},
119131
...overrides,
120132
} as Task;
121133
}
@@ -136,6 +148,7 @@ describe("useChatTitleGenerator", () => {
136148
vi.clearAllMocks();
137149
useTitleGenerationStore.setState({ byTaskId: {} });
138150
mockIsAuthenticated.value = true;
151+
mockCurrentUser.value = { id: 1 };
139152
mockPrompts.value = [];
140153
mockSessionSummary.value = undefined;
141154
mockTitleAttachmentPaths.value = [];
@@ -152,6 +165,14 @@ describe("useChatTitleGenerator", () => {
152165
expect(mockGenerateTitle).not.toHaveBeenCalled();
153166
});
154167

168+
it("waits for task creator identity before generating", () => {
169+
mockCurrentUser.value = undefined;
170+
171+
renderHook(() => useChatTitleGenerator(createTask()));
172+
173+
expect(mockGenerateTitle).not.toHaveBeenCalled();
174+
});
175+
155176
it("generates title from the saved task description before prompt events arrive", async () => {
156177
mockGenerateTitle.mockResolvedValue({
157178
title: "Fix login bug",
@@ -171,6 +192,34 @@ describe("useChatTitleGenerator", () => {
171192
title: "Fix login bug",
172193
});
173194
});
195+
expect(mockGenerateTitle).toHaveBeenCalledWith("Fix the login bug", {
196+
resolveGithubPrTitles: true,
197+
});
198+
});
199+
200+
it("does not resolve GitHub metadata from a teammate's description", async () => {
201+
mockGenerateTitle.mockResolvedValue({
202+
title: "Review PR #123",
203+
summary: "Reviewing a pull request",
204+
});
205+
206+
renderHook(() =>
207+
useChatTitleGenerator(
208+
createTask({
209+
created_by: {
210+
id: 2,
211+
uuid: "user-2",
212+
email: "teammate@example.com",
213+
},
214+
}),
215+
),
216+
);
217+
218+
await waitFor(() => {
219+
expect(mockGenerateTitle).toHaveBeenCalledWith("Fix the login bug", {
220+
resolveGithubPrTitles: false,
221+
});
222+
});
174223
});
175224

176225
it("generates title when the task has no title yet", async () => {

packages/ui/src/features/sessions/hooks/useChatTitleGenerator.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useService } from "@posthog/di/react";
1515
import type { Task } from "@posthog/shared/domain-types";
1616
import { useOptionalAuthenticatedClient } from "@posthog/ui/features/auth/authClient";
1717
import { useAuthStateValue } from "@posthog/ui/features/auth/store";
18+
import { useCurrentUser } from "@posthog/ui/features/auth/useCurrentUser";
1819
import {
1920
sessionStoreSetters,
2021
useSessionStore,
@@ -49,6 +50,7 @@ export function useChatTitleGenerator(task: Task): void {
4950
);
5051
const queryClient = useQueryClient();
5152
const client = useOptionalAuthenticatedClient();
53+
const { data: currentUser } = useCurrentUser({ client });
5254
const isAuthenticated = useAuthStateValue(
5355
(state) => state.status === "authenticated" && !!state.cloudRegion,
5456
);
@@ -62,7 +64,7 @@ export function useChatTitleGenerator(task: Task): void {
6264
});
6365

6466
useEffect(() => {
65-
if (!isAuthenticated) return;
67+
if (!isAuthenticated || (task.created_by && !currentUser)) return;
6668

6769
const bookkeeping = titleGenerationStoreApi.get(taskId);
6870
if (bookkeeping.inFlight) return;
@@ -111,7 +113,9 @@ export function useChatTitleGenerator(task: Task): void {
111113
attachmentPaths,
112114
);
113115
const result = await titleGenerator.generateTitleAndSummary(content, {
114-
resolveGithubPrTitles: shouldGenerateFromPrompts,
116+
resolveGithubPrTitles:
117+
shouldGenerateFromPrompts ||
118+
(!!currentUser && task.created_by?.id === currentUser.id),
115119
});
116120
if (result) {
117121
// Drop the stash once a title has been successfully produced so the
@@ -197,5 +201,6 @@ export function useChatTitleGenerator(task: Task): void {
197201
queryClient,
198202
sessionService,
199203
titleGenerator,
204+
currentUser,
200205
]);
201206
}

0 commit comments

Comments
 (0)