n8n-io/n8n · warning

SDK_UNSOLICITED_STICKY

SDK_UNSOLICITED_STICKY

Error message

Do not add sticky() / stickyNote nodes unless the user explicitly asked for canvas notes. Put explanations in the chat reply instead.

What it means

The linter flags any call to the `sticky()` factory. Sticky notes are canvas annotations, not workflow logic; the SDK guidance is to add them only when the user has explicitly asked for a note on the canvas. Unprompted `sticky()` calls clutter the workflow and push explanation into a place users miss, so the walker emits `SDK_UNSOLICITED_STICKY` (workflow-sdk-lint.ts:251-262) for every `sticky(...)` invocation, regardless of arguments.

Source

Thrown at packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts:254

				if (!isPropertyName && !isObjectKey && !isSafeMethodObject) {
					issues.push(
						lintIssue({
							code: 'SDK_FORBIDDEN_CONSTRUCT',
							message: `Global '${node.name}' is unavailable in SDK builder code. Move runtime logic to a Code node or expr().`,
							...locationOf(node),
							lintTarget: 'sdk',
						}),
					);
				}
			}

			if (node.type !== 'CallExpression') return;
			const call = node;

			if (call.callee.type === 'Identifier' && call.callee.name === 'sticky') {
				issues.push(
					lintIssue({
						code: 'SDK_UNSOLICITED_STICKY',
						message:
							'Do not add sticky() / stickyNote nodes unless the user explicitly asked for canvas notes. ' +
							'Put explanations in the chat reply instead.',
						...locationOf(call),
						lintTarget: 'sdk',
					}),
				);
			}

			if (
				call.callee.type === 'MemberExpression' &&
				!call.callee.computed &&
				call.callee.property.type === 'Identifier'
			) {
				const method = call.callee.property.name;

				if (method === 'onTrue' || method === 'onFalse') {
					// Only count direct `ifNode.onTrue(...)` / `ifNode.onFalse(...)`.

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Remove the `sticky()` call entirely and put the explanation in the chat reply / PR description instead.
  2. Only re-add `sticky()` if the user has explicitly asked for canvas notes; if so, accept that the lint warning is expected and review it during PR.
  3. If a note is genuinely required, prefer `sticky()` with concise user-facing text and no internal commentary.

Example fix

// before
sticky('This node fetches users from the API and filters by active status.');
workflow().to(httpNode).to(filterNode);

// after — explanation goes in the chat reply, not the canvas
workflow().to(httpNode).to(filterNode);
Defensive patterns

Strategy: validation

Validate before calling

import { parse } from 'acorn';

function hasUnsolicitedSticky(builderSource: string): boolean {
  const ast = parse(builderSource, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
  let found = false;
  walk(ast, (n) => {
    if (n.type === 'CallExpression' && n.callee.type === 'Identifier' && n.callee.name === 'sticky') {
      found = true;
    }
  });
  return found;
}

Type guard

import type { Node, CallExpression } from 'estree';
const isStickyCall = (n: Node): n is CallExpression =>
  n.type === 'CallExpression' && n.callee.type === 'Identifier' && n.callee.name === 'sticky';

Prevention

When it happens

Trigger: Calling `sticky('explanation text')`, `stickyNote(...)`, or any `CallExpression` whose callee is an `Identifier` named `sticky` in builder source. There is no argument-based exemption — any such call fires.

Common situations: AI agents annotate generated workflows with sticky notes explaining each step. Agents use `sticky()` as a substitute for in-chat explanations. Treating sticky notes as inline documentation.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/0248508b1db183ab. Report an issue: GitHub.