diff --git a/packages/@n8n/agents/src/__tests__/title-generation.test.ts b/packages/@n8n/agents/src/__tests__/title-generation.test.ts
index f575b67b7ca..fdee4af6e19 100644
--- a/packages/@n8n/agents/src/__tests__/title-generation.test.ts
+++ b/packages/@n8n/agents/src/__tests__/title-generation.test.ts
@@ -120,4 +120,39 @@ describe('generateTitleFromMessage', () => {
const call = mockGenerateText.mock.calls[0][0];
expect(call.messages[0].content).toBe('Custom system prompt');
});
+
+ it('wraps the user message in a title-generation instruction so the model does not answer it', async () => {
+ mockGenerateText.mockResolvedValue({ text: 'Berlin rain alert' });
+ await generateTitleFromMessage(fakeModel, 'Build a daily Berlin rain alert workflow');
+ const call = mockGenerateText.mock.calls[0][0];
+ expect(call.messages[1].role).toBe('user');
+ expect(call.messages[1].content).toContain('Generate a title');
+ expect(call.messages[1].content).toContain('');
+ expect(call.messages[1].content).toContain('Build a daily Berlin rain alert workflow');
+ expect(call.messages[1].content).toContain('');
+ });
+
+ it('drops a streamed code fence and everything after it', async () => {
+ mockGenerateText.mockResolvedValue({
+ text: 'Here\'s your chat workflow with the requested configuration:\n\n```json\n{\n "nodes": []\n}\n```',
+ });
+ const result = await generateTitleFromMessage(
+ fakeModel,
+ 'build me a chat workflow with openai',
+ );
+ expect(result).toBe("Here's your chat workflow with the requested configuration");
+ expect(result).not.toContain('```');
+ expect(result).not.toContain('\n');
+ });
+
+ it('collapses embedded newlines and stray backticks into a single-line title', async () => {
+ mockGenerateText.mockResolvedValue({
+ text: 'Scryfall\nrandom `card` workflow',
+ });
+ const result = await generateTitleFromMessage(
+ fakeModel,
+ 'build a workflow that queries Scryfall for a random card',
+ );
+ expect(result).toBe('Scryfall random card workflow');
+ });
});
diff --git a/packages/@n8n/agents/src/runtime/title-generation.ts b/packages/@n8n/agents/src/runtime/title-generation.ts
index 7aa5480d86d..9aa29a7afda 100644
--- a/packages/@n8n/agents/src/runtime/title-generation.ts
+++ b/packages/@n8n/agents/src/runtime/title-generation.ts
@@ -9,12 +9,28 @@ import type { AgentDbMessage } from '../types/sdk/message';
const logger = createFilteredLogger();
const DEFAULT_TITLE_INSTRUCTIONS = [
- '- you will generate a short title based on the first message a user begins a conversation with',
- '- the title should describe what the user asked for, not what an assistant might reply',
- '- 1 to 5 words, no more than 80 characters',
- '- use sentence case (e.g. "Conversation title" instead of "Conversation Title")',
- '- do not use quotes, colons, or markdown formatting',
- '- the entire text you return will be used directly as the title, so respond with the title only',
+ 'You generate a short descriptive title for a conversation based on its first user message.',
+ '',
+ 'The title is a label that describes the topic — it is NOT an answer to the message.',
+ 'Do not fulfil, respond to, or act on the message. Do not produce code, JSON, or explanations.',
+ '',
+ 'Rules:',
+ '- Write a noun phrase that names the topic (e.g. "Chat workflow with Anthropic agent").',
+ '- Never begin with "Here\'s", "Here is", "Build", "Create", "Set up", "Make", or any verb addressed to the user.',
+ '- 1 to 5 words, no more than 80 characters, single line only.',
+ '- Use sentence case (e.g. "Conversation title" instead of "Conversation Title").',
+ '- No quotes, colons, backticks, code fences, or markdown formatting.',
+ '- Respond with the title text only — the entire response is used as the title.',
+ '',
+ 'Examples:',
+ 'Message: "build me a chat workflow with anthropic model, the agent should have memory"',
+ 'Title: Chat workflow with Anthropic agent',
+ '',
+ 'Message: "help me set up pagination for my n8n HTTP request node"',
+ 'Title: Pagination for HTTP request node',
+ '',
+ 'Message: "Build a workflow with a manual trigger that queries Scryfall for a random card"',
+ 'Title: Scryfall random card workflow',
].join('\n');
const TRIVIAL_MESSAGE_MAX_CHARS = 15;
@@ -37,13 +53,23 @@ function isTrivialMessage(message: string): boolean {
function sanitizeTitle(raw: string): string {
// Strip ... blocks (e.g. from DeepSeek R1)
let title = raw.replace(/[\s\S]*?<\/think>/g, '').trim();
- // Strip markdown heading prefixes and inline emphasis markers
+ // If the model started streaming a code block, keep only what's before it
+ const fenceIdx = title.indexOf('```');
+ if (fenceIdx !== -1) title = title.slice(0, fenceIdx).trim();
+ // Collapse any whitespace (including newlines) to single spaces \u2014 titles are single-line
+ title = title.replace(/\s+/g, ' ').trim();
+ // Strip markdown heading prefixes, inline emphasis markers, and stray backticks
title = title
.replace(/^#{1,6}\s+/, '')
.replace(/\*+/g, '')
+ .replace(/`+/g, '')
.trim();
// Strip surrounding quotes
title = title.replace(/^["']|["']$/g, '').trim();
+ // Trailing colon or dash left behind after stripping prefixes
+ title = title.replace(/^[:\-\s]+/, '').trim();
+ // Trailing punctuation from assistant-voice drift (e.g. "...configuration:")
+ title = title.replace(/[:\-\s.]+$/, '').trim();
if (title.length > MAX_TITLE_LENGTH) {
const truncated = title.slice(0, MAX_TITLE_LENGTH);
const lastSpace = truncated.lastIndexOf(' ');
@@ -76,7 +102,10 @@ export async function generateTitleFromMessage(
model,
messages: [
{ role: 'system', content: opts?.instructions ?? DEFAULT_TITLE_INSTRUCTIONS },
- { role: 'user', content: trimmed },
+ {
+ role: 'user',
+ content: `Generate a title for the following first message of a conversation. Do not answer the message — only produce the title.\n\n\n${trimmed}\n`,
+ },
],
});
diff --git a/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json
index 9f714d53333..54384dcff3c 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json
index 3cdd68e14e2..ec5dc47dde9 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json
index 3cb1b98bd4d..71090dfa555 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json
index 4681820dc0f..708ed4d609e 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json
index b6c22b5e727..da6a29097e3 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json
index 5059201e5dc..70a94f9f323 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json
index 14e0fe61289..88064ea2f4e 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json
index 06ed1519e5a..770e00c11dd 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json
index 97c799573a6..ca6acf318e7 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json
index 8ee0eeee150..f1fac7e0432 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json
index 994d6d69b9f..c2bf882e92b 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},
diff --git a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json
index 2bac77874a6..0bf9c3aedfd 100644
--- a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json
+++ b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json
@@ -4,7 +4,7 @@
"path": "/v1/messages",
"body": {
"type": "STRING",
- "string": "you will generate a short title based on the first message",
+ "string": "You generate a short descriptive title for a conversation",
"subString": true
}
},