{"record":{"id":"0248508b1db183ab","repo":"n8n-io/n8n","slug":"sdk-unsolicited-sticky","errorCode":"SDK_UNSOLICITED_STICKY","errorMessage":"Do not add sticky() / stickyNote nodes unless the user explicitly asked for canvas notes. Put explanations in the chat reply instead.","messagePattern":"Do not add sticky\\(\\) / stickyNote nodes unless the user explicitly asked for canvas notes\\. Put explanations in the chat reply instead\\.","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts","lineNumber":254,"sourceCode":"\t\t\t\tif (!isPropertyName && !isObjectKey && !isSafeMethodObject) {\n\t\t\t\t\tissues.push(\n\t\t\t\t\t\tlintIssue({\n\t\t\t\t\t\t\tcode: 'SDK_FORBIDDEN_CONSTRUCT',\n\t\t\t\t\t\t\tmessage: `Global '${node.name}' is unavailable in SDK builder code. Move runtime logic to a Code node or expr().`,\n\t\t\t\t\t\t\t...locationOf(node),\n\t\t\t\t\t\t\tlintTarget: 'sdk',\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (node.type !== 'CallExpression') return;\n\t\t\tconst call = node;\n\n\t\t\tif (call.callee.type === 'Identifier' && call.callee.name === 'sticky') {\n\t\t\t\tissues.push(\n\t\t\t\t\tlintIssue({\n\t\t\t\t\t\tcode: 'SDK_UNSOLICITED_STICKY',\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t'Do not add sticky() / stickyNote nodes unless the user explicitly asked for canvas notes. ' +\n\t\t\t\t\t\t\t'Put explanations in the chat reply instead.',\n\t\t\t\t\t\t...locationOf(call),\n\t\t\t\t\t\tlintTarget: 'sdk',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tcall.callee.type === 'MemberExpression' &&\n\t\t\t\t!call.callee.computed &&\n\t\t\t\tcall.callee.property.type === 'Identifier'\n\t\t\t) {\n\t\t\t\tconst method = call.callee.property.name;\n\n\t\t\t\tif (method === 'onTrue' || method === 'onFalse') {\n\t\t\t\t\t// Only count direct `ifNode.onTrue(...)` / `ifNode.onFalse(...)`.","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/workflow-sdk/src/lint/sdk/workflow-sdk-lint.ts#L236-L272","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the `sticky()` call entirely and put the explanation in the chat reply / PR description instead.","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.","If a note is genuinely required, prefer `sticky()` with concise user-facing text and no internal commentary."],"exampleFix":"// before\nsticky('This node fetches users from the API and filters by active status.');\nworkflow().to(httpNode).to(filterNode);\n\n// after — explanation goes in the chat reply, not the canvas\nworkflow().to(httpNode).to(filterNode);","handlingStrategy":"validation","validationCode":"import { parse } from 'acorn';\n\nfunction hasUnsolicitedSticky(builderSource: string): boolean {\n  const ast = parse(builderSource, { ecmaVersion: 'latest', sourceType: 'module', locations: true });\n  let found = false;\n  walk(ast, (n) => {\n    if (n.type === 'CallExpression' && n.callee.type === 'Identifier' && n.callee.name === 'sticky') {\n      found = true;\n    }\n  });\n  return found;\n}","typeGuard":"import type { Node, CallExpression } from 'estree';\nconst isStickyCall = (n: Node): n is CallExpression =>\n  n.type === 'CallExpression' && n.callee.type === 'Identifier' && n.callee.name === 'sticky';","tryCatchPattern":null,"preventionTips":["Default to putting explanations in the chat reply, never on the canvas.","Only emit `sticky()` when the user has explicitly asked for a note.","Run the SDK linter on generated workflows and treat `SDK_UNSOLICITED_STICKY` as a generation bug."],"tags":["sdk-lint","workflow-sdk","sticky-note","ux","agent-guidance","builder-code"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}