{"record":{"id":"b6109b72f13bec3d","repo":"windmill-labs/windmill","slug":"special-modules-must-be-provided-via-preprocessor","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/core.ts","lineNumber":732,"sourceCode":"\t\t\t\t| undefined\n\t\t\tparsedGroups = parseOptionalJsonArg(groups, 'groups') as FlowGroup[] | null | undefined\n\t\t\tparsedNotes = parseOptionalJsonArg(notes, 'notes') as FlowNote[] | null | undefined\n\t\t\tif (parsedModules === null) {\n\t\t\t\tparsedModules = undefined\n\t\t\t}\n\t\t\tif (parsedSchema === null) {\n\t\t\t\tparsedSchema = undefined\n\t\t\t}\n\n\t\t\tconst aiProviderWarnings: string[] = []\n\t\t\tif (parsedModules !== undefined) {\n\t\t\t\tconst aiProviders = await getAiAgentProviderCatalogFor(workspace, parsedModules)\n\t\t\t\tparsedModules = validateFlowModules(parsedModules, { aiProviders, aiProviderWarnings })\n\t\t\t\tconst reservedIds = collectAllFlowModuleIdsFromModules(parsedModules).filter(\n\t\t\t\t\t(id) => id === SPECIAL_MODULE_IDS.PREPROCESSOR || id === SPECIAL_MODULE_IDS.FAILURE\n\t\t\t\t)\n\t\t\t\tif (reservedIds.length > 0) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t'Special modules must be provided via preprocessor_module and failure_module, not inside modules'\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (parsedSchema !== undefined) {\n\t\t\t\tparsedSchema = validateFlowSchema(parsedSchema)\n\t\t\t}\n\n\t\t\tparsedPreprocessorModule = validateSpecialFlowModule(\n\t\t\t\tparsedPreprocessorModule,\n\t\t\t\t'preprocessor_module'\n\t\t\t)\n\t\t\tparsedFailureModule = validateSpecialFlowModule(parsedFailureModule, 'failure_module')\n\n\t\t\tif (parsedGroups !== undefined || parsedNotes !== undefined) {\n\t\t\t\tconst effectiveModules =\n\t\t\t\t\tparsedModules ?? helpers.getFlowAndSelectedId().flow.value.modules ?? []\n\t\t\t\tconst moduleIdsForGroups = new Set(collectAllFlowModuleIdsFromModules(effectiveModules))","sourceCodeStart":714,"sourceCodeEnd":750,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/core.ts#L714-L750","documentation":"When setting a whole flow via the set-flow tool, modules destined for the flow's preprocessor or failure handler must be passed in the dedicated preprocessor_module / failure_module fields, not as entries inside the modules array. After validation, the tool scans all module IDs for the reserved special IDs (preprocessor/failure) and throws this error if any appear inline in modules.","triggerScenarios":"Calling set_flow (flowTools setFlow path) with a modules array containing a module whose id equals SPECIAL_MODULE_IDS.PREPROCESSOR or SPECIAL_MODULE_IDS.FAILURE, instead of supplying it via the preprocessor_module/failure_module arguments.","commonSituations":"The AI read a full backend flow payload (which embeds preprocessor/failure inside the modules list at the top level of the flow value) and echoed it back verbatim; hand-written flow JSON copied from an export that keeps special modules inline.","solutions":["Remove the preprocessor/failure module from the modules array and pass it via the preprocessor_module or failure_module field.","Keep the module's content identical — only its location changes from modules[] to the dedicated field.","Regenerate the payload from the editable flow JSON view, which already separates special modules."],"exampleFix":"// before\nsetFlow({ modules: [...other, { id: 'preprocessor', value: {...} }] })\n// after\nsetFlow({ modules: [...other], preprocessor_module: { id: 'preprocessor', value: {...} } })","handlingStrategy":"validation","validationCode":"const RESERVED = ['preprocessor', 'failure'] // SPECIAL_MODULE_IDS\nconst inline = modules.filter((m) => RESERVED.includes(m.id))\nif (inline.length) throw new Error(`Move ${inline.map((m) => m.id).join(',')} out of modules into preprocessor_module/failure_module`)","typeGuard":"function usesReservedIdsInline(modules: FlowModule[]): boolean {\n  return modules.some((m) => m.id === 'preprocessor' || m.id === 'failure')\n}","tryCatchPattern":"try {\n  await setFlow(payload)\n} catch (e) {\n  if (e.message.includes('Special modules must be provided via')) {\n    const fixed = liftSpecialModules(payload) // move matching modules to dedicated fields\n    await setFlow(fixed)\n  } else throw e\n}","preventionTips":["Never echo raw backend flow payloads; use the editable flow JSON shape","Treat 'preprocessor' and 'failure' IDs as reserved everywhere in payloads","Build payloads from buildEditableFlowJson, which separates special modules"],"tags":["copilot","flow-validation","schema","reserved-ids"],"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"}