diff --git a/packages/core/src/sessions/titleGeneratorService.test.ts b/packages/core/src/sessions/titleGeneratorService.test.ts
index 956fd897ac..dfa5829191 100644
--- a/packages/core/src/sessions/titleGeneratorService.test.ts
+++ b/packages/core/src/sessions/titleGeneratorService.test.ts
@@ -220,6 +220,42 @@ describe("generateTitleAndSummary", () => {
expect(result?.title).toBe("Fix login bug");
});
+ it("does not parse SUMMARY in a PR title as the summary", async () => {
+ prompt.mockResolvedValue({
+ content:
+ "TITLE: Review PR #123: Fix SUMMARY: parsing\nSUMMARY: Fixing title and summary parsing.",
+ });
+
+ const result = await makeService().generateTitleAndSummary(
+ '',
+ );
+
+ expect(result).toEqual({
+ title: "Review PR #123: Fix SUMMARY: parsing",
+ summary: "Fixing title and summary parsing.",
+ });
+ });
+
+ it("instructs the model to include existing GitHub PR titles", async () => {
+ prompt.mockResolvedValue({
+ content:
+ "TITLE: Review PR #123: Fix login redirect\nSUMMARY: Reviewing the existing pull request.",
+ });
+
+ await makeService().generateTitleAndSummary(
+ '',
+ );
+
+ expect(prompt).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.objectContaining({
+ system: expect.stringContaining(
+ "the generated TITLE MUST include both the PR number and the PR title verbatim",
+ ),
+ }),
+ );
+ });
+
it("returns null on error", async () => {
prompt.mockRejectedValue(new Error("network error"));
const result = await makeService().generateTitleAndSummary("some content");
diff --git a/packages/core/src/sessions/titleGeneratorService.ts b/packages/core/src/sessions/titleGeneratorService.ts
index 537317fb02..7913c2f64b 100644
--- a/packages/core/src/sessions/titleGeneratorService.ts
+++ b/packages/core/src/sessions/titleGeneratorService.ts
@@ -40,6 +40,7 @@ Title rules:
- Remove: the, this, my, a, an
- If possible, start with action verbs (Fix, Implement, Analyze, Debug, Update, Research, Review)
- Keep exact: technical terms, numbers, filenames, HTTP codes, PR numbers
+- GitHub PR rule: If the content contains a with a non-empty title, the generated TITLE MUST include both the PR number and the PR title verbatim. This rule overrides the 6-word title limit. Never replace the PR title with a generic phrase. Before responding, verify that both values appear in TITLE.
- Never assume tech stack
- Only output "Untitled" if the input is completely null/missing, not just unclear
- If the input is a URL (e.g. a GitHub issue link, PR link, or any web URL), generate a title based on what you can infer from the URL structure (repo name, issue/PR number, etc.). Never say you cannot access URLs or ask the user for more information.
@@ -58,6 +59,7 @@ Title examples:
- "Update user documentation for new API endpoints" → Update API documentation
- "Research competitor pricing strategies for our product" → Research competitor pricing
- "Review pull request #123" → Review pull request #123
+- "" → Review PR #123: Fix login redirect
- "debug 500 errors in production" → Debug production 500 errors
- "why is the payment flow failing" → Analyze payment flow failure
- "So how about that weather huh" → Weather chat
@@ -173,7 +175,7 @@ export class TitleGeneratorService {
const text = result.content.trim();
const titleMatch = text.match(/^TITLE:\s*(.+?)(?:\n|$)/m);
- const summaryMatch = text.match(/SUMMARY:\s*([\s\S]+)$/m);
+ const summaryMatch = text.match(/^SUMMARY:\s*([\s\S]+)$/m);
const title =
titleMatch?.[1]