{"record":{"id":"517f0f2cf1267984","repo":"windmill-labs/windmill","slug":"special-modules-must-be-provided-via-preprocessor-517f0f","errorCode":null,"errorMessage":"Special modules must be provided via preprocessor_module and failure_module, not inside modules","messagePattern":"Special modules must be provided via preprocessor_module and failure_module, not inside modules","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts","lineNumber":263,"sourceCode":"\t\t\tif (moduleId) {\n\t\t\t\treturn `Module \"${moduleId}\" -> ${fieldPath}: ${message}`\n\t\t\t}\n\t\t\treturn `${path.join('.')}: ${message}`\n\t\t})\n\n\t\tthrow new Error(`Invalid flow modules:\\n${errors.join('\\n')}`)\n\t}\n\n\tconst ids = collectAllFlowModuleIdsFromModules(parsedModules)\n\tif (ids.length !== new Set(ids).size) {\n\t\tthrow new Error('Duplicate module IDs found in flow')\n\t}\n\n\tconst reservedIds = ids.filter(\n\t\t(id) => id === SPECIAL_MODULE_IDS.PREPROCESSOR || id === SPECIAL_MODULE_IDS.FAILURE\n\t)\n\tif (reservedIds.length > 0) {\n\t\tthrow new Error(\n\t\t\t'Special modules must be provided via preprocessor_module and failure_module, not inside modules'\n\t\t)\n\t}\n\n\t// Not expressible in the schema: `provider` is required only when the step is standalone, and\n\t// making AiAgent a conditional union breaks the FlowModuleValue discriminated union it belongs to.\n\tconst providerless = collectProviderlessAgentIds(parsedModules)\n\tif (providerless.length > 0) {\n\t\tthrow new Error(\n\t\t\t`AI agent modules ${providerless\n\t\t\t\t.map((id) => `\"${id}\"`)\n\t\t\t\t.join(\n\t\t\t\t\t', '\n\t\t\t\t)} need a provider input transform, or an \"agent\" path linking them to a saved agent`\n\t\t)\n\t}\n\n\t// An agent tool's `summary` is the name the LLM sees; the worker rejects anything outside","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts#L245-L281","documentation":"As the final structural check in validateFlowModules, the collected module IDs are scanned for the reserved preprocessor/failure special IDs. If any module inside the modules array uses one of those reserved IDs, this error is thrown: special modules must be delivered through the dedicated preprocessor_module / failure_module fields of the flow payload, never inline in modules.","triggerScenarios":"validateFlowModules (via flowTools or the 'modules' setter) receiving a modules array that contains a module with id === SPECIAL_MODULE_IDS.PREPROCESSOR or SPECIAL_MODULE_IDS.FAILURE, instead of passing it through the preprocessor_module/failure_module arguments.","commonSituations":"Echoing a raw backend flow payload where special modules sit in the modules list; porting flow JSON from exports/API responses into the editable representation without lifting the special modules into their dedicated fields.","solutions":["Extract the module with the reserved id from modules and pass it as preprocessor_module (for the preprocessor id) or failure_module (for the failure id).","If the special module is unchanged, omit it from the modules payload entirely and only supply the dedicated field.","Derive the payload from buildEditableFlowJson output, which already separates special modules from the modules array."],"exampleFix":"// before\nvalidateFlowModules([...mods, { id: 'failure', value: {...} }])\n// after\nsetFlowJson({ modules: mods, failure_module: { id: 'failure', value: {...} } })","handlingStrategy":"validation","validationCode":"const RESERVED = ['preprocessor', 'failure']\nconst offenders = modules.filter((m) => RESERVED.includes(m.id))\nif (offenders.length) {\n  throw new Error('Move reserved modules to preprocessor_module/failure_module fields')\n}","typeGuard":"function hasReservedIdsInline(modules: FlowModule[]): boolean {\n  return modules.some((m) => m.id === 'preprocessor' || m.id === 'failure')\n}","tryCatchPattern":"try {\n  validateFlowModules(modules)\n} catch (e) {\n  if (e.message.includes('Special modules must be provided via')) {\n    const { modules: rest, pre, fail } = splitSpecialModules(modules)\n    return setFlowJson({ modules: rest, preprocessor_module: pre, failure_module: fail })\n  } else throw e\n}","preventionTips":["Treat preprocessor/failure IDs as reserved inside modules[]","Always deliver special modules via the dedicated payload fields","Derive payloads from buildEditableFlowJson so separation is done for you"],"tags":["validation","reserved-ids","flow-modules","special-modules"],"backgroundTag":"reserved-module-id-placement","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"}