Skip to content

feat(core): Improve node grouping instructions for AI Assistant and MCP (no-changelog) - #37729

Draft
miguelsaddress wants to merge 18 commits into
masterfrom
ado-5802-ai-better-groups
Draft

feat(core): Improve node grouping instructions for AI Assistant and MCP (no-changelog)#37729
miguelsaddress wants to merge 18 commits into
masterfrom
ado-5802-ai-better-groups

Conversation

@miguelsaddress

@miguelsaddress miguelsaddress commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

The AI Assistant and MCP clients group workflows poorly. They draw frames on trivial
workflows, put one or two nodes in a group, and write titles that name the nodes inside
instead of the outcome. The instructions live in two shared strings in
packages/@n8n/workflow-sdk/src/prompts/sdk-reference/sdk-language.ts: NODE_GROUPS_REFERENCE
holds the mechanics and the validity rules, and GROUPING_GUIDANCE holds the judgement.
Both surfaces import both strings.

GROUPING_GUIDANCE had no numbers, only "larger workflows" and "small workflows", which an
agent resolves in its own favour. It now requires an explicit grouping decision while the
code is written, decides whether to group by counting top-level items rather than by shape
or size, says where to cut when a stage ends in a branch, and holds titles and descriptions
to one outcome each. NODE_GROUPS_REFERENCE keeps only the mechanics, no longer presents the
description as optional, and states that a dropped-group warning means the boundary was
wrong rather than that the stage stays ungrouped.

The Instance AI builder skill kept its own summary of the guidance, which had already
drifted. It now injects the shared constant through a placeholder resolved when the skill
source is loaded, so there is one source of truth and it reaches both the materialized
SKILL.md and the load_skill tool.

How to test

pnpm --filter @n8n/workflow-sdk test sdk-language
pnpm --filter @n8n/instance-ai test materialize-runtime-skills
pnpm --filter n8n test get-workflow-best-practices
pnpm --filter n8n test get-workflow-sdk-reference

For a manual check, build workflows of varying size with the AI Assistant and read the
nodeGroups of each result. A trivial workflow should get no groups. A workflow with more
than 7 top-level items should get one group per stage, with outcome-first titles of 2 to 4
words and a description that adds to the title. Group output varies between runs, so run
each prompt at least three times. Test prompts and expected results follow in a comment.

Related Linear tickets, Github issues, and Community forum posts

https://linear.app/n8n/issue/ADO-5802

Review / Merge checklist

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)

🤖 PR Summary generated by AI

Review in cubic

miguelsaddress and others added 9 commits September 2, 2026 14:40
…CP (no-changelog)

Grouping guidance had no numbers ("larger workflows", "small workflows"), so
agents over-grouped: frames on trivial workflows, groups of one or two nodes,
titles naming the nodes inside instead of the outcome.

GROUPING_GUIDANCE now carries the judgement — 3 to 5 groups for a medium
workflow, at most 7 items at the canvas top level, a group as one business
outcome rather than a technical category, 2-4 word outcome-first titles, and a
description on every group that adds to the title instead of restating it.
NODE_GROUPS_REFERENCE keeps only the mechanics, and no longer calls the
description optional or asks for a single sentence, both of which undercut the
guidance.

Structural rules stay sourced from NODE_GROUPING_RULES and are not restated in
the guidance; a test walks that constant to keep the two copies from drifting.

Both surfaces already import both constants, so Instance AI and MCP pick this
up with no further change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…builder skill (no-changelog)

The builder skill carried its own summary of the grouping guidance, and it had
drifted: no group counts, and a "one-sentence description" instruction that the
shared guidance now contradicts. The skill is read in full whenever it loads, so
that stale copy is what the agent acts on, while the updated guidance sat in a
knowledge-base file it had to choose to open.

The skill now injects the shared GROUPING_GUIDANCE at materialization time
through a {{GROUPING_GUIDANCE_PLACEHOLDER}} token, so there is one source of
truth and the criteria arrive in context without depending on an extra read.
What stays hand-written is one line of mechanics and a pointer to the node-groups
reference for the rules that make a group valid.

The reference itself is not injected: it is mechanical, it is only needed while
writing a .group(...) call, and it already ships inside the SDK language
reference the builder has to read anyway.

Tests cover the injected text, that no placeholder is left unresolved in any
materialized skill, and that the skill source carries no grouping criteria of its
own, so a hand-written copy cannot come back unnoticed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-changelog)

An observed build skipped grouping on a workflow that clearly warranted three
groups. Asked why, the agent said it had not decided against groups — it never
reached the step, and then applied the criteria correctly on request. The
criteria were fine; grouping was simply not part of the build.

The shared guidance now opens by requiring an explicit decision while the code is
written: declare the groups, or conclude the workflow does not warrant any.
Deciding against groups stays a first-class answer, so this does not trade
omission for grouping by rote.

The AI Assistant builder skill gets the sequencing the guidance cannot carry: the
Mandatory Process step that produces the SDK code now says to decide grouping
there, since .group(...) lives in the source and cannot be added after the build,
and Completion refuses to report a build as done before that decision. MCP has no
equivalent process to hook into, which is why the obligation itself lives in the
shared string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gelog)

An observed build left 9 top-level items ungrouped and justified it as "linear
and small". Its reasoning showed why: it read "purely linear" as a standalone
exemption, treated 9 nodes as small on its own authority, believed groups were
for parallel blocks, and never counted against the 7-item ceiling. Told about
the ceiling, it agreed it applied and proposed the right three groups.

The decision is now a count rather than a judgement about shape or size: count
the trigger plus every node or existing group, and more than 7 means grouping is
required. Linearity is explicitly not an exemption, and sequential stages are
named as the normal case for grouping rather than the exception. The ceiling
moves to a post-grouping check, keeping the tie-break toward fewer groups.

Boundaries that come out at one or two nodes each are now called too fine, with
the instruction to widen them — the same build rejected grouping because it only
ever considered two-node stages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(no-changelog)

The grouping guidance was injected while rendering the materialized SKILL.md,
which left two holes.

`load_skill` serves `skill.instructions` straight from the source, so an agent
loading the builder skill through the tool received the literal
{{GROUPING_GUIDANCE_PLACEHOLDER}} and no guidance at all. Only the filesystem
copy of SKILL.md carried the real text.

`hashSkill` also hashes the raw instructions, so changing the shared constant
left `skillsHash` untouched. Caches and workspace manifests keyed on that hash
kept serving a previously rendered SKILL.md with the older guidance.

Substitution now happens as the skill source is loaded, through a
`transformInstructions` option on `loadRuntimeSkillSourceFromDirectory`. It runs
before the registry is built, so both delivery routes carry the same text and the
hash covers it.

The test that asserted the placeholder survives into the loaded skill was
asserting the bug. It now reads SKILL.md from disk to check the source keeps no
grouping criteria of its own, and two new tests cover the load_skill route and
the pre-hash substitution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hangelog)

Two findings from real builds.

A description that opens by restating its title reads as compliant but wastes the
only words the user sees: a collapsed group clips the text, so "Validates the
shipping address against the Google API and reduces it to a verdict" under the
title "Validate via Google" shows the reader nothing new before it cuts off. The
guidance now forbids opening with the title and says to lead with the detail that
earns its place, giving the clipping as the reason.

The 7-item ceiling was written as a hard requirement, which invites an agent to
invent an invalid group or split a coherent stage to hit the number. Branches
fanning straight out of a trigger cannot share a group, so some graphs cannot
reach 7 at all. It is now a target: landing an item or two over is fine when the
shape forbids better, and neither breaking the validity rules nor splitting a
stage that reads as one objective is an acceptable way to reach it. The tie-break
becomes fewer and larger groups.

Deciding *whether* to group stays the hard count it was, so this does not reopen
the "it is linear, so it needs no groups" excuse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A build described its agent group as "AI agent with Postgres chat memory drafts a
reply, searching the docs vector store and opening a Linear ticket". The
collapsed group clips the text, so the only words the reader saw were the node
inventory plus a restatement of the title — everything already visible on the
canvas.

The lead-with-the-detail rule now forbids opening on what the group is built
from, and carries that description as the counter-example. A fourth worked
example shows the same group written from the input and the output instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tay high (no-changelog)

Both prompt strings are tightened: the naming and description sections become
inline paragraphs and the reference leads with its MUST. Five findings from real
builds then land on top, so the guidance still ends smaller than it started.

A build wrote three groups and lost two to NODE_GROUP_DROPPED. Its stages ended
in an IF that stopped one way and continued the other, which makes the IF a
non-leaf with an edge out of its own group. Nothing told it where to cut, so the
guidance now does: end the group before the branch node, or leave that node and
its stop path outside.

The same build read its two warnings as licence to publish the stage ungrouped.
The reference now says a warning means the boundary was wrong, not that the
stage stays ungrouped — redraw and build again, without re-emitting the same
invalid group.

Another build skipped the grouping decision entirely and rationalised it
afterwards, exactly the failure the decision rule was added for. That rule had
been folded into the paragraph that defines what a group is; it gets its own
paragraph back.

The 7-item target invited an agent to chase a number a branchy graph cannot
reach. Staying above 7 is now allowed only when every remaining top-level item
is a group already or cannot join one without breaking a validity rule, which is
stricter than the target it replaces: it has to be checked item by item.

Titles came out as step pairs — "Charge & ship", "Store & alert" — which also
drags the description into restating them. Titles now name one outcome, with the
fix shown rather than just banned. It is explicitly a preference: a two-verb
title beats splitting a coherent stage or leaving it ungrouped, because canvas
coverage matters more than a title.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@n8n-assistant

n8n-assistant Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR review overview

Based on ownership of the 12 changed files in this PR:

Ownership Files owned Share Source code Test files Misc
@n8n-io/ai-assistant 10 83% +194 / -28 +458 / -9 +18 / -11
@n8n-io/ai 1 8% +16 / -3 +0 / -0 +0 / -0
@n8n-io/catalysts 1 8% +12 / -3 +0 / -0 +0 / -0
Total 12 100% +222 / -34 +458 / -9 +18 / -11

@miguelsaddress miguelsaddress self-assigned this Sep 3, 2026
cubic-dev-ai[bot]

This comment was marked as outdated.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Instance AI Discovery Eval ✅

Branch: ado-5802-ai-better-groups · Commit: bddd10e6ff40f585aaded23254bce121b0daeba6

Eval output
$ tsx evaluations/discovery/cli.ts --trials 3 --fail-on-zero-pass
Running 17 discovery scenario(s) × 3 trial(s) (model: anthropic/claude-sonnet-4-6, concurrency: 3).

▸ config-evals-skill-loading ... (node:4862) [DEP0205] DeprecationWarning: `module.register()` is deprecated. Use `module.registerHooks()` instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
✓ 3/3 passed (100%)
▸ data-table-natural-list-skill-loading ... ✓ 3/3 passed (100%)
▸ data-table-skill-loading ... ✓ 3/3 passed (100%)
▸ data-table-workflow-skill-loading ... ✓ 3/3 passed (100%)
▸ google-oauth-credential-setup ... ✓ 3/3 passed (100%)
▸ http-node-config-no-browser ... ✓ 3/3 passed (100%)
▸ mcp-broken-connection-reconnect ... ✓ 3/3 passed (100%)
▸ mcp-connect-unconnected-service ... ✓ 2/3 passed (67%)
▸ mcp-declined-tool-call-no-reconnect ... ✓ 3/3 passed (100%)
▸ mcp-no-registry-match ... ✓ 3/3 passed (100%)
▸ mcp-not-offered-for-workflow-build ... ✓ 3/3 passed (100%)
▸ mcp-uses-connected-server-tools ... ✓ 3/3 passed (100%)
▸ oauth-with-computer-use-disabled ... ✓ 3/3 passed (100%)
▸ screenshot-dashboard ... ✓ 3/3 passed (100%)
▸ slack-oauth-credential-setup ... ✓ 3/3 passed (100%)
▸ workflow-builder-no-agent-builder-leak ... ✓ 3/3 passed (100%)
▸ workflow-builder-no-credential-ask ... ✓ 3/3 passed (100%)

=== Summary ===
Scenarios: 17/17 above threshold (67%)
Trials: 50/51 passed (98%)
Total time: 1615.5s

@n8n-assistant n8n-assistant Bot added the n8n team Authored by the n8n team label Sep 3, 2026
…s a ban on grouping (no-changelog)

A build skipped grouping and explained why: the SDK_UNSOLICITED_STICKY warning
fired, it stripped the sticky note, and it then treated every cosmetic canvas
construct as "do not add unless asked". Groups went with the stickies.

The warning only ever mentioned stickies, but the generalisation is a fair
reading: stickies and groups are the two cosmetic canvas constructs, and "unless
the user explicitly asked" is a strong frame that arrives mid-build, in response
to the agent's own code. That is far more salient than a paragraph in the skill.

The message now says it applies to sticky notes only and that node groups are
not optional decoration, so the grouping decision survives the warning. This is
likely the root cause of the omissions seen earlier, where builds described
skipping "the visual organisation step" rather than deciding against groups.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as outdated.

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

…one (no-changelog)

A build drew two groups, lost one to NODE_GROUP_DROPPED, described the loss as
cosmetic and shipped with the top level above 7. Asked about it, it quoted both
rules it had broken and said the skill was loaded and reloaded throughout the
build. So this was not a delivery or knowledge gap: it read the rules and
under-applied them.

The grouping decision already fires reliably, because it is a step in the
Mandatory Process. What did not fire was checking the result, which lived only in
the guidance. Completion now demands that check: a dropped-group warning means
redraw the boundary and build again, and staying above 7 top-level items means
naming each remaining item and why it cannot join a group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as outdated.

Both the reference and the builder skill said a dropped-group warning means the
boundary was wrong. Five of the causes are not about the boundary at all: a
duplicate group id or name, an empty group, a member that does not exist, and a
node claimed by two groups. Faced with a duplicate name, an agent following that
wording would go and redraw a boundary that was already correct.

Both texts now say the warning names what was invalid, give the non-boundary
causes as examples, and tell the agent to fix what the warning reports. A
warning still never means the stage stays ungrouped, and the same invalid group
must not be re-emitted. A test fails if the old wording returns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as outdated.

… (no-changelog)

The previous commit flipped what the test asserts — the reference must not tell
the agent to redraw the boundary — but left the title and comment saying the
opposite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as outdated.

miguelsaddress and others added 2 commits September 3, 2026 17:41
… branch (no-changelog)

The branch rule offered two ways out and missed the best one. A branch node
whose paths both leave the group is a leaf inside the selection, and both edges
come from that one node, so it counts as a single exit and the group is valid.
Only a branch node that keeps one path inside and sends the other out is
rejected.

That missing option matters: keeping the branch node inside leaves the fewest
boxes on the canvas. An observed build ended its validation group before the
majority check and put the check plus both alert nodes at the top level, where
the check itself could have stayed inside.

The three options are now listed, best first. The ceiling also becomes a shared
constant, TOP_LEVEL_ITEM_CEILING in n8n-workflow, interpolated into the guidance
next to the description cap so the number has one home.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…elog)

Grouping rules now live in three places an agent can read: the guidance, the
Mandatory Process step that writes the code, and a Completion checkpoint that
asks it to count the top level and justify anything left over. Builds quote all
three accurately when asked, group correctly when they act, and skip the count
anyway. The step that produces something fires; the step that reviews what was
produced does not.

Dropped-group warnings do land, so the count is now delivered the same way.
build-workflow reports the boxes a reader would see with every group collapsed,
once past the ceiling, and names what is still ungrouped — which is the list the
checkpoint asks the agent to produce. It runs after invalid groups are dropped,
so it describes the canvas that was saved.

Nothing is reported at or under the ceiling, so a correct build stays quiet. Sub-
nodes ride with their parent and stickies belong to the user, so neither counts.
The trigger counts as a box but is never listed, since no group may hold one.
Each of those has a test: a false positive here teaches the agent to ignore the
warning.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 114.68kB (0.18%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
editor-ui-esm 63.32MB 114.68kB (0.18%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: editor-ui-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/src-*.js 5.61kB 3.37MB 0.17%
assets/src-*.js 4.87kB 805.52kB 0.61%
assets/worker-*.js -3.2MB 18.44kB -99.43%
assets/worker-*.js 3.2MB 3.22MB 17352.85% ⚠️
assets/workflows.store-*.js 598 bytes 832.5kB 0.07%
assets/AgentBuilderView-*.js 6.15kB 749.6kB 0.83%
assets/InstanceAiThreadView-*.js 862 bytes 613.8kB 0.14%
assets/InstanceAiThreadView-*.css -90 bytes 543.99kB -0.02%
assets/InstanceAiEmptyView-*.js 350 bytes 463.6kB 0.08%
assets/AgentBuilderView-*.css 8.71kB 425.06kB 2.09%
assets/NodeCreator-*.css 9 bytes 280.5kB 0.0%
assets/AgentSkillModal-*.js 97 bytes 243.43kB 0.04%
assets/useRootStore-*.js 386 bytes 185.34kB 0.21%
assets/NodeCreator-*.js -1.28kB 179.54kB -0.71%
assets/router-*.js 34 bytes 161.6kB 0.02%
assets/canvas.eventBus-*.js 439 bytes 120.92kB 0.36%
assets/agents.eventBus-*.js 1.44kB 109.15kB 1.34%
assets/instanceAi.store-*.js 738 bytes 93.17kB 0.8%
assets/ProjectSettings-*.js 6.4kB 83.82kB 8.26% ⚠️
assets/dist-*.js 787 bytes 4.03kB 24.31% ⚠️
assets/AgentToolConfigModal-*.js 1.38kB 77.63kB 1.81%
assets/WorkflowBuilderUnavailableNotice-*.js 147 bytes 67.99kB 0.22%
assets/ToolsConnectionModal-*.js 1.02kB 64.22kB 1.61%
assets/SettingsOpenTelemetryView-*.js 2.69kB 60.3kB 4.66%
assets/ToolsConnectionModal-*.css 220 bytes 59.08kB 0.37%
assets/ProjectSettings-*.css 8.46kB 54.96kB 18.2% ⚠️
assets/AgentToolConfigModal-*.css -244 bytes 54.23kB -0.45%
assets/settings.store-*.js 116 bytes 53.97kB 0.22%
assets/WorkerView-*.js 298 bytes 45.31kB 0.66%
assets/constants-*.js 134 bytes 44.73kB 0.3%
assets/AppSidebar-*.js -18 bytes 44.51kB -0.04%
assets/useNodeHelpers-*.js 904 bytes 37.41kB 2.48%
assets/ProjectHeader-*.js 2.74kB 36.34kB 8.14% ⚠️
assets/AgentToolsConnectionModalWrapper-*.js 628 bytes 27.68kB 2.32%
assets/InstanceAiToolsConnectionModalWrapper-*.js 743 bytes 25.02kB 3.06%
assets/AgentsListView-*.js 485 bytes 21.25kB 2.34%
assets/ToolIcon-*.css 142 bytes 19.69kB 0.73%
assets/PromotionSelectModal-*.css (New) 19.51kB 19.51kB 100.0% 🚀
assets/ProjectHeader-*.css 295 bytes 18.69kB 1.6%
assets/ToolIcon-*.js 729 bytes 18.46kB 4.11%
assets/PromotionSelectModal-*.js (New) 18.01kB 18.01kB 100.0% 🚀
assets/SigninView-*.js 267 bytes 16.82kB 1.61%
assets/useInstanceAiHandoff-*.js 260 bytes 15.46kB 1.71%
assets/useMcpServerAdapter-*.js 1.16kB 13.42kB 9.49% ⚠️
assets/agentSkill-*.css 121 bytes 11.53kB 1.06%
assets/thread-*.js 249 bytes 9.3kB 2.75%
assets/McpRegistrySuggestionFooter-*.css (New) 8.92kB 8.92kB 100.0% 🚀
assets/useAgentApi-*.js 60 bytes 8.82kB 0.68%
assets/posthog.store-*.js 972 bytes 8.28kB 13.3% ⚠️
assets/useSettingsItems-*.js -105 bytes 7.25kB -1.43%
assets/agentSkill-*.js 474 bytes 6.95kB 7.32% ⚠️
assets/useMcpServerConnect-*.js 608 bytes 5.99kB 11.29% ⚠️
assets/useAgentToolCatalog-*.js 24 bytes 5.44kB 0.44%
assets/McpRegistrySuggestionFooter-*.js (New) 4.72kB 4.72kB 100.0% 🚀
assets/promotions.api-*.js (New) 1.48kB 1.48kB 100.0% 🚀
assets/otel.constants-*.js 82 bytes 1.42kB 6.15% ⚠️
assets/usePromotionsEnabled-*.js (New) 900 bytes 900 bytes 100.0% 🚀

cubic-dev-ai[bot]

This comment was marked as outdated.

… (no-changelog)

The shared rule said a group takes at most one incoming and one outgoing main
connection. The validator counts nodes: it collects the members that take input
from outside and fails when there is more than one, and likewise for output. So
several connections may reach a single entry member — a Merge fed by three
branches is a valid entry — and the old wording ruled that out. An agent reading
it literally rejects groups the save path would accept.

The guidance also gains the play for a stage whose work fans out. Such a stage
cannot be one group while the branches start outside it, because each branch head
takes input from outside; keeping the node they fan out from inside makes the
fan-out internal. When the branches leave the trigger, which can never be a
member, the stage needs a preparation step to serve as the entry, or the group
starts where the branches reconverge. A build that hit this wall grouped nothing
at all afterwards, including a post-merge stretch that was valid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cubic-dev-ai[bot]

This comment was marked as outdated.

…hangelog)

A build called grouping a finishing touch and skipped it, echoing the word this
doc used: groups were described as cosmetic and purely organisational three
times. The reference now pairs no execution semantics with why groups still
matter, so the two never travel apart.

The boundary rules move into their own block, stating the one-member-per-side
limit once and covering the exit side and nodes already in another group. A
rejected group no longer reads as a closed stage: three builds took one
rejection and left the stage ungrouped where a smaller slice was valid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 2 files (changes from recent commits).

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Shadow auto-approve: would auto-approve. Refines AI assistant/MCP node-grouping guidance into counting-based rules, injects the shared constant into the builder skill via a placeholder, and adds an informational top-level-item warning; extensive tests pin the prompt text, placeholder substitution, and warning behavior.

Re-trigger cubic

…(no-changelog)

Every reminder that arrives after the save is ignored — the completion checkpoint
and the build warning both. The one an agent has acted on is a source lint issue,
at the same informational severity: it stripped a sticky note because
SDK_UNSOLICITED_STICKY told it to. The difference is phase, not severity, so the
box count moves to the lint the Mandatory Process makes it run before its first
build.

The count is exact at source level: node() and trigger() draw boxes, the sub-node
builders do not, and a .group() is one box plus its members. No connection
heuristics.

The message says to group while building and to leave an existing layout alone,
since the lint cannot tell authoring from editing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 2 files (changes from recent commits).

Confidence score: 3/5

  • In packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts, omitting ifElse, switchCase, merge, and splitInBatches from boxBuilders lets oversized workflows avoid the grouping warning; include all main-node canvas types.
  • The lint analysis in packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts can warn about boxes that toJSON() never serializes when source handles outnumber exported builders, creating unnecessary grouping guidance; count only handles reachable from the exported builder.
  • The .group() analysis in packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts treats valid GroupMember[] variables and spread arrays as ungrouped, causing false warnings for compact canvases; resolve member bindings before counting.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts">

<violation number="1" location="packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts:170">
P1: When a workflow uses `ifElse`, `switchCase`, `merge`, or `splitInBatches`, this set leaves those canvas nodes out of `boxBuilders`, so oversized workflows can pass without the grouping warning. Include all main-node composite builders in the counted set while continuing to exclude sub-nodes and sticky notes.</violation>

<violation number="2" location="packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts:190">
P2: When source declares more than seven node handles but adds seven or fewer to the exported builder, this warning reports boxes that `toJSON()` never serializes and urges unnecessary grouping. Count handles reachable from the exported workflow builder instead of every factory call.</violation>

<violation number="3" location="packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts:205">
P2: When `.group()` receives a valid `GroupMember[]` variable or spread array, this pass treats every member as ungrouped and can warn for a compact canvas. Resolve the member binding before counting, or avoid claiming those nodes are ungrouped when the list cannot be statically resolved.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

/** Lint a prepared, parsed SDK AST (imports/TS already stripped). */
/** Builders that put a box on the canvas. Sub-node builders (`languageModel`,
* `tool`, `memory`, …) and `sticky` ride with their parent, so they are absent. */
const CANVAS_BOX_BUILDERS = new Set(['node', 'trigger']);

@cubic-dev-ai cubic-dev-ai Bot Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a workflow uses ifElse, switchCase, merge, or splitInBatches, this set leaves those canvas nodes out of boxBuilders, so oversized workflows can pass without the grouping warning. Include all main-node composite builders in the counted set while continuing to exclude sub-nodes and sticky notes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts, line 170:

<comment>When a workflow uses `ifElse`, `switchCase`, `merge`, or `splitInBatches`, this set leaves those canvas nodes out of `boxBuilders`, so oversized workflows can pass without the grouping warning. Include all main-node composite builders in the counted set while continuing to exclude sub-nodes and sticky notes.</comment>

<file context>
@@ -164,6 +165,76 @@ function rangeContains(range: SourceRange, line: number, column: number): boolea
 /** Lint a prepared, parsed SDK AST (imports/TS already stripped). */
+/** Builders that put a box on the canvas. Sub-node builders (`languageModel`,
+ *  `tool`, `memory`, …) and `sticky` ride with their parent, so they are absent. */
+const CANVAS_BOX_BUILDERS = new Set(['node', 'trigger']);
+
+/**
</file context>
Suggested change
const CANVAS_BOX_BUILDERS = new Set(['node', 'trigger']);
const CANVAS_BOX_BUILDERS = new Set(['node', 'trigger', 'ifElse', 'switchCase', 'merge', 'splitInBatches']);
Fix with cubic


const { callee } = node;
if (callee.type === 'Identifier' && CANVAS_BOX_BUILDERS.has(callee.name)) {
boxBuilders++;

@cubic-dev-ai cubic-dev-ai Bot Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When source declares more than seven node handles but adds seven or fewer to the exported builder, this warning reports boxes that toJSON() never serializes and urges unnecessary grouping. Count handles reachable from the exported workflow builder instead of every factory call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts, line 190:

<comment>When source declares more than seven node handles but adds seven or fewer to the exported builder, this warning reports boxes that `toJSON()` never serializes and urges unnecessary grouping. Count handles reachable from the exported workflow builder instead of every factory call.</comment>

<file context>
@@ -164,6 +165,76 @@ function rangeContains(range: SourceRange, line: number, column: number): boolea
+
+		const { callee } = node;
+		if (callee.type === 'Identifier' && CANVAS_BOX_BUILDERS.has(callee.name)) {
+			boxBuilders++;
+			return;
+		}
</file context>
Fix with cubic


groupCount++;
const members = node.arguments[1];
if (members?.type !== 'ArrayExpression') {

@cubic-dev-ai cubic-dev-ai Bot Sep 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When .group() receives a valid GroupMember[] variable or spread array, this pass treats every member as ungrouped and can warn for a compact canvas. Resolve the member binding before counting, or avoid claiming those nodes are ungrouped when the list cannot be statically resolved.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts, line 205:

<comment>When `.group()` receives a valid `GroupMember[]` variable or spread array, this pass treats every member as ungrouped and can warn for a compact canvas. Resolve the member binding before counting, or avoid claiming those nodes are ungrouped when the list cannot be statically resolved.</comment>

<file context>
@@ -164,6 +165,76 @@ function rangeContains(range: SourceRange, line: number, column: number): boolea
+
+		groupCount++;
+		const members = node.arguments[1];
+		if (members?.type !== 'ArrayExpression') {
+			return;
+		}
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed n8n team Authored by the n8n team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant