{"record":{"id":"ad4a1744fc1501c3","repo":"windmill-labs/windmill","slug":"pipeline-node-content-must-declare-the-pipeline-an","errorCode":null,"errorMessage":"Pipeline node content must declare the pipeline annotation on its own comment line (`// pipeline`, or `-- pipeline` for SQL / `# pipeline` for Python).","messagePattern":"Pipeline node content must declare the pipeline annotation on its own comment line \\(`// pipeline`, or `-- pipeline` for SQL / `# pipeline` for Python\\)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/assets/AssetGraph/pipelineAiHelpers.ts","lineNumber":160,"sourceCode":"\tfunction assertInFolder(path: string) {\n\t\tconst folder = folderName()\n\t\tif (folder && !path.startsWith(`f/${folder}/`)) {\n\t\t\t// Name the corrected path rather than only the required prefix: the model\n\t\t\t// otherwise re-sends variants of the same wrong path. A path that is the\n\t\t\t// folder itself has no node leaf to reuse.\n\t\t\tconst leaf = path.split('/').filter(Boolean).pop()\n\t\t\tconst node = !leaf || leaf === folder ? '<node_name>' : leaf\n\t\t\tthrow new Error(\n\t\t\t\t`Pipeline nodes must be in the open folder — use 'f/${folder}/${node}' (got '${path}').`\n\t\t\t)\n\t\t}\n\t}\n\n\t// A pipeline node IS its `// pipeline` annotation (it's what makes the deployed\n\t// script a pipeline member). Reject content that lacks it so a staged draft\n\t// isn't a non-member script the model can't see is broken until deploy.\n\tfunction assertPipelineAnnotation(content: string) {\n\t\tif (!parsePipelineAnnotations(content).inPipeline) {\n\t\t\tthrow new Error(\n\t\t\t\t`Pipeline node content must declare the pipeline annotation on its own comment line ` +\n\t\t\t\t\t`(\\`// pipeline\\`, or \\`-- pipeline\\` for SQL / \\`# pipeline\\` for Python).`\n\t\t\t)\n\t\t}\n\t}\n\n\tfunction buildContext(): PipelineContext {\n\t\tconst graph = deps.getResolvedGraph()\n\t\tconst drafts = deps.getDrafts()\n\t\tconst nodes: PipelineNodeSummary[] = graph.runnables\n\t\t\t.filter((r) => r.usage_kind === 'script')\n\t\t\t.map((r) => {\n\t\t\t\tconst draft = drafts.get(r.path)\n\t\t\t\tconst writes = graph.edges\n\t\t\t\t\t.filter(\n\t\t\t\t\t\t(e) =>\n\t\t\t\t\t\t\te.runnable_kind === 'script' &&","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/assets/AssetGraph/pipelineAiHelpers.ts#L142-L178","documentation":"A Windmill pipeline node is defined by the `// pipeline` annotation comment in its script content (with per-language variants `-- pipeline` for SQL and `# pipeline` for Python). assertPipelineAnnotation parses the content and throws this error when the annotation is absent, so a staged draft can't silently become a non-member script.","triggerScenarios":"Writing or staging pipeline node content via the AI helpers where the content string lacks a standalone comment line `// pipeline` (or the SQL/Python equivalents) — e.g. the model generated a plain script body, the annotation was stripped by a formatter, or it was embedded inside another statement/string rather than on its own comment line.","commonSituations":"Model-generated content omitting the header comment; a code formatter removing or reflowing the comment; writing `#pipeline` (no space), inline trailing annotation like `foo() // pipeline` on a code line, or using the wrong comment token for the script language; content written with a block comment instead of a line comment.","solutions":["Add the annotation as its own first comment line matching the language: `// pipeline` (JS/TS), `-- pipeline` (SQL), `# pipeline` (Python)","Ensure the annotation is a standalone line — not trailing code or inside a block comment/string","Verify the comment token matches the script's language attribute","Re-run the write/stage call after adding the annotation"],"exampleFix":"// before (JS/TS node)\nexport async function main() { ... }\n// after\n// pipeline\nexport async function main() { ... }","handlingStrategy":"validation","validationCode":"const ANNOTATION = /(^|\\n)\\s*(\\/\\/|--|#) pipeline\\s*(\\n|$)/\nfunction hasPipelineAnnotation(content: string): boolean {\n  return ANNOTATION.test(content)\n}\nif (!hasPipelineAnnotation(draftContent)) {\n  draftContent = '// pipeline\\n' + draftContent // or '# pipeline' for Python, '-- pipeline' for SQL\n}","typeGuard":"function declaresPipeline(content: string): boolean {\n  return parsePipelineAnnotations(content).inPipeline\n}","tryCatchPattern":"try {\n  await stageNode(path, content)\n} catch (e) {\n  if (e instanceof Error && e.message.includes('pipeline annotation')) {\n    return stageNode(path, prependAnnotation(content, language))\n  }\n  throw e\n}","preventionTips":["Always emit the annotation as the first line of generated pipeline content","Match the comment token to the script language: // for JS/TS, -- for SQL, # for Python","Keep it on its own line — no trailing code on the same line, no block comments","Disable formatters/linters that strip leading comments from generated drafts"],"tags":["validation","pipeline","annotations","windmill"],"backgroundTag":"missing-pipeline-annotation","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}