windmill-labs/windmill · error
Special modules must be provided via preprocessor_module and
Error message
Special modules must be provided via preprocessor_module and failure_module, not inside modules
What it means
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.
Source
Thrown at frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts:263
if (moduleId) {
return `Module "${moduleId}" -> ${fieldPath}: ${message}`
}
return `${path.join('.')}: ${message}`
})
throw new Error(`Invalid flow modules:\n${errors.join('\n')}`)
}
const ids = collectAllFlowModuleIdsFromModules(parsedModules)
if (ids.length !== new Set(ids).size) {
throw new Error('Duplicate module IDs found in flow')
}
const reservedIds = ids.filter(
(id) => id === SPECIAL_MODULE_IDS.PREPROCESSOR || id === SPECIAL_MODULE_IDS.FAILURE
)
if (reservedIds.length > 0) {
throw new Error(
'Special modules must be provided via preprocessor_module and failure_module, not inside modules'
)
}
// Not expressible in the schema: `provider` is required only when the step is standalone, and
// making AiAgent a conditional union breaks the FlowModuleValue discriminated union it belongs to.
const providerless = collectProviderlessAgentIds(parsedModules)
if (providerless.length > 0) {
throw new Error(
`AI agent modules ${providerless
.map((id) => `"${id}"`)
.join(
', '
)} need a provider input transform, or an "agent" path linking them to a saved agent`
)
}
// An agent tool's `summary` is the name the LLM sees; the worker rejects anything outsideView on GitHub (pinned to e474e8803c)
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.
Example fix
// before
validateFlowModules([...mods, { id: 'failure', value: {...} }])
// after
setFlowJson({ modules: mods, failure_module: { id: 'failure', value: {...} } }) Defensive patterns
Strategy: validation
Validate before calling
const RESERVED = ['preprocessor', 'failure']
const offenders = modules.filter((m) => RESERVED.includes(m.id))
if (offenders.length) {
throw new Error('Move reserved modules to preprocessor_module/failure_module fields')
} Type guard
function hasReservedIdsInline(modules: FlowModule[]): boolean {
return modules.some((m) => m.id === 'preprocessor' || m.id === 'failure')
} Try / catch
try {
validateFlowModules(modules)
} catch (e) {
if (e.message.includes('Special modules must be provided via')) {
const { modules: rest, pre, fail } = splitSpecialModules(modules)
return setFlowJson({ modules: rest, preprocessor_module: pre, failure_module: fail })
} else throw e
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Flow modules must be an array
- Invalid flow modules:\n${errors.join('\n')}
- Duplicate module IDs found in flow
- result.substring(__RESULT_ERR_PREFIX.length)
- Invalid migration name '${name}': use only letters, digits,
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/517f0f2cf1267984.
Report an issue: GitHub.