immich-app/immich · error · Error
Unable to infer workflow event type from steps
Error message
Unable to infer workflow event type from steps
What it means
execute cannot determine which WorkflowType a workflow's steps correspond to: after iterating the steps and matching them against known target types, no type could be inferred. This is an internal invariant violation indicating misconfigured workflow data — steps that map to no known event type.
Solutions
- Inspect the workflow's steps in the database/definition and verify each step's method maps to a known workflow type.
- Delete or re-create the broken workflow via the workflow editor so steps are re-validated against current methods.
- Update Immich (or the workflow definitions) if a version migration renamed step methods; run any pending migrations.
- If programmatically creating workflows, always run them through resolveAndValidateSteps before persisting so bad steps are rejected early.
Example fix
// before const steps = workflow.steps; // possibly stale/unvalidated await execute(workflowId, steps); // after const steps = await resolveAndValidateSteps(workflow.trigger, workflow.steps); if (steps.length === 0) return; // skip empty/invalid workflows await execute(workflowId, steps);
Defensive patterns
Strategy: validation
Validate before calling
const inferType = (steps: Step[]) =>
steps.map((s) => TARGET_TYPES.find((t) => matches(s, t))).find(Boolean);
if (!steps.length || !inferType(steps)) {
logger.warn('skipping workflow with uninferrable steps');
return;
} Type guard
const hasKnownType = (steps: Step[]): steps is Step[] & { length: number } =>
steps.length > 0 && steps.every((s) => KNOWN_TYPES.includes(s.type as T)); Try / catch
try {
await execute(workflowId, steps);
} catch (e) {
if (e.message === 'Unable to infer workflow event type from steps') {
logger.error(`workflow ${workflowId} has unrecognizable steps; skipping`);
return;
}
throw e;
} Prevention
- Validate steps through resolveAndValidateSteps before persisting workflows.
- Run migrations when upgrading so step methods are renamed consistently.
- Reject workflow definitions with zero steps at creation time.
- Re-validate imported workflows against the current method registry.
When it happens
Trigger: handleAssetTrigger invokes execute for a workflow whose stored steps array is empty or whose steps' methods resolve to no recognized WorkflowType, so the `type` variable remains undefined after the matching loop.
Common situations: Workflow definitions saved by an older version whose step methods were renamed/removed; hand-edited or imported workflow JSON with unknown step payloads; a workflow created with zero steps; plugin methods removed from the registry while workflows still reference them.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Album must have an owner
- authToken is required
- Cannot remove the last album owner
- Hostname did not match any listed in methods[].allowedHosts…
- Invalid token
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/54f9148419b123ab.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/workflow-execution.service.ts:408
getHandler: (type: T) => ExecuteOptions<T> | undefined,
) {
const workflow = await this.workflowRepository.getForWorkflowRun(workflowId);
if (!workflow) {
return;
}
// TODO infer from steps
let type: T | undefined;
for (const targetType of Object.values(WorkflowType)) {
const isMissing = workflow.steps.some((step) => !step.types.includes(targetType));
if (!isMissing) {
type = targetType as unknown as T;
break;
}
}
if (!type) {
throw new Error('Unable to infer workflow event type from steps');
}
const handler = getHandler(type);
if (!handler) {
this.logger.error(`Misconfigured workflow ${workflowId}: no handler for type ${type}`);
return;
}
const { read, write } = handler;
const readResult = await read(type);
let data = readResult.data;
const runId = crypto.randomUUID();
for (const step of workflow.steps) {
try {
const payload: WorkflowEventPayload<typeof type> = {
trigger: workflow.trigger,
type,View on GitHub (pinned to e55ac299a4)