Skip to content

Commit 0ebd291

Browse files
JoseBraclaude
andcommitted
fix(ai-builder): Handle data table name conflict gracefully instead of looping (no-changelog)
When the builder creates a workflow involving a data table, it creates the table in run #1 then tries to create it again in run #2 (rebuild phase). The DataTableNameConflictError causes the agent to retry in a loop until the build times out. Fix: catch the conflict error and return `denied: true` with a reason guiding the agent to use the existing table. The agent already handles denied responses gracefully. Also adds family-assistant eval test case (Telegram + AI agent + calendar + shopping list + web search) which exercises this code path. Before: BUILD FAILED after 175s of looping After: BUILD SUCCESS in ~3 min, 3/3 scenarios passing Ref: https://linear.app/n8n/issue/AI-2358 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dfdc6d2 commit 0ebd291

File tree

2 files changed

+60
-4
lines changed

2 files changed

+60
-4
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"prompt": "I want to build a personal family assistant that can handle our calendars, maintain a shopping list and search the web. I want to interact with it via telegram. Configure all nodes as completely as possible and don't ask me for credentials, I'll set them up later.",
3+
"complexity": "complex",
4+
"tags": ["build", "telegram", "ai-agent", "google-calendar", "web-search"],
5+
"triggerType": "webhook",
6+
"scenarios": [
7+
{
8+
"name": "add-shopping-item",
9+
"description": "User asks to add an item to the shopping list via Telegram",
10+
"dataSetup": "The Telegram trigger receives a message: 'Add milk and eggs to the shopping list'. The AI agent processes the request and adds the items. All tool calls return success responses.",
11+
"successCriteria": "The workflow executes without errors. The AI agent receives the Telegram message and identifies it as a shopping list request. The items are added to the shopping list. A confirmation message is sent back via Telegram."
12+
},
13+
{
14+
"name": "check-calendar",
15+
"description": "User asks about upcoming calendar events",
16+
"dataSetup": "The Telegram trigger receives a message: 'What do I have on my calendar this week?'. The Google Calendar API returns 2 events: 'Team standup' on Monday at 10am and 'Dentist appointment' on Wednesday at 3pm. The AI agent processes and responds.",
17+
"successCriteria": "The workflow executes without errors. The AI agent receives the Telegram message, queries the calendar, and sends back a summary of the upcoming events via Telegram."
18+
},
19+
{
20+
"name": "web-search",
21+
"description": "User asks to search the web for something",
22+
"dataSetup": "The Telegram trigger receives a message: 'Search for the best Italian restaurants near me'. The web search returns a few results. The AI agent summarizes and responds.",
23+
"successCriteria": "The workflow executes without errors. The AI agent receives the Telegram message, performs a web search, and sends back a summary of the results via Telegram."
24+
}
25+
]
26+
}

packages/@n8n/instance-ai/src/tools/data-tables/create-data-table.tool.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,20 @@ export const createDataTableResumeSchema = z.object({
2828
approved: z.boolean(),
2929
});
3030

31+
/**
32+
* Check if an error (or its cause chain) is a DataTableNameConflictError.
33+
* The error class lives in packages/cli so we can't import it directly —
34+
* instead we match on the class name through the cause chain.
35+
*/
36+
function isNameConflictError(error: unknown): boolean {
37+
let current: unknown = error;
38+
while (current instanceof Error) {
39+
if (current.constructor.name === 'DataTableNameConflictError') return true;
40+
current = (current as Error & { cause?: unknown }).cause;
41+
}
42+
return false;
43+
}
44+
3145
export function createCreateDataTableTool(context: InstanceAiContext) {
3246
return createTool({
3347
id: 'create-data-table',
@@ -91,10 +105,26 @@ export function createCreateDataTableTool(context: InstanceAiContext) {
91105
}
92106

93107
// State 3: Approved or always_allow — execute
94-
const table = await context.dataTableService.create(input.name, input.columns, {
95-
projectId: input.projectId,
96-
});
97-
return { table };
108+
try {
109+
const table = await context.dataTableService.create(input.name, input.columns, {
110+
projectId: input.projectId,
111+
});
112+
return { table };
113+
} catch (error) {
114+
// If table already exists, guide the agent to use the existing one
115+
// rather than throwing — which would cause the agent to retry in a loop
116+
if (isNameConflictError(error)) {
117+
const tables = await context.dataTableService.list({ projectId: input.projectId });
118+
const existing = tables.find((t) => t.name === input.name);
119+
if (existing) {
120+
return {
121+
denied: true,
122+
reason: `Table "${input.name}" already exists. Use list-data-tables to find it and get-data-table-schema to check its columns.`,
123+
};
124+
}
125+
}
126+
throw error;
127+
}
98128
},
99129
});
100130
}

0 commit comments

Comments
 (0)