{"record":{"id":"2c92e4be009bebe6","repo":"n8n-io/n8n","slug":"concurrencymode-must-be-sequential-or-concurren","errorCode":null,"errorMessage":"concurrencyMode must be 'sequential' or 'concurrent', got ${String(lifecycleOptions.concurrencyMode)}","messagePattern":"concurrencyMode must be 'sequential' or 'concurrent', got (.+?)","errorType":"validation","errorClass":"InvalidLifecycleOptionsError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/scheduler/src/core/factory.ts","lineNumber":176,"sourceCode":"\t\t'materializerTimeoutSeconds',\n\t\t'executorTimeoutSeconds',\n\t\t'reaperTimeoutSeconds',\n\t\t'retentionTimeoutSeconds',\n\t] as const;\n\tfor (const key of durationKeys) {\n\t\tconst value = lifecycleOptions[key];\n\t\tif (!(Number.isFinite(value) && value > 0)) {\n\t\t\tthrow new InvalidLifecycleOptionsError(\n\t\t\t\t`${key} must be a positive number of seconds, got ${value}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (\n\t\tlifecycleOptions.concurrencyMode !== 'sequential' &&\n\t\tlifecycleOptions.concurrencyMode !== 'concurrent'\n\t) {\n\t\tthrow new InvalidLifecycleOptionsError(\n\t\t\t`concurrencyMode must be 'sequential' or 'concurrent', got ${String(lifecycleOptions.concurrencyMode)}`,\n\t\t);\n\t}\n\n\tconst { maxConcurrentPasses } = lifecycleOptions;\n\tif (!(Number.isInteger(maxConcurrentPasses) && maxConcurrentPasses >= 1)) {\n\t\tthrow new InvalidLifecycleOptionsError(\n\t\t\t`maxConcurrentPasses must be a positive integer, got ${maxConcurrentPasses}`,\n\t\t);\n\t}\n\n\tconst emit = (level: SchedulerEventLevel, message: string, context: Record<string, unknown>) => {\n\t\t// The sink is the reporting channel itself: if it throws (a broken logger),\n\t\t// there is nothing left to report through, and the pass that emitted must\n\t\t// not be broken by its own observability.\n\t\ttry {\n\t\t\tonEvent?.({ level, message, context });\n\t\t} catch {","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/scheduler/src/core/factory.ts#L158-L194","documentation":"Thrown by createScheduler when lifecycleOptions.concurrencyMode is neither 'sequential' nor 'concurrent'. The mode decides whether executor passes run one-at-a-time or overlap; an unknown value would branch into undefined behavior downstream, so the factory rejects it before any pass starts.","triggerScenarios":"Passing lifecycle.concurrencyMode: 'parallel', 'concurrent-', undefined-after-defaults (withDefaults did not set it), null, or a value with trailing whitespace ('concurrent ').","commonSituations":"A typo in config (e.g. 'concurrent' vs 'concurent'); a config schema that allowed arbitrary strings; an upgrade that renamed the mode and left stale config; whitespace from an env var not trimmed.","solutions":["Set lifecycle.concurrencyMode to exactly 'sequential' or 'concurrent'.","Trim and lowercase config-derived values: (raw ?? '').trim().toLowerCase().","If the value comes from an env var, validate against an allowlist in your config loader.","Omit concurrencyMode to accept the DEFAULT_LIFECYCLE_OPTIONS default."],"exampleFix":"// before\ncreateScheduler({ lifecycle: { concurrencyMode: cfg.mode } }); // cfg.mode = 'parallel' -> throws\n\n// after - normalize against an allowlist\nconst MODES = ['sequential', 'concurrent'] as const;\nconst concurrencyMode = MODES.includes(cfg.mode as any)\n  ? (cfg.mode as typeof MODES[number])\n  : 'concurrent';\ncreateScheduler({ lifecycle: { concurrencyMode } });","handlingStrategy":"type-guard","validationCode":"const MODES = ['sequential', 'concurrent'] as const;\ntype ConcurrencyMode = typeof MODES[number];\n\nfunction normalizeMode(raw: unknown): ConcurrencyMode {\n  const v = typeof raw === 'string' ? raw.trim().toLowerCase() : '';\n  return (MODES as readonly string[]).includes(v) ? (v as ConcurrencyMode) : 'concurrent';\n}","typeGuard":"function isConcurrencyMode(v: unknown): v is 'sequential' | 'concurrent' {\n  return v === 'sequential' || v === 'concurrent';\n}","tryCatchPattern":null,"preventionTips":["Constrain the config schema to the enum so invalid values fail at config-load, not at scheduler creation.","Trim and lowercase env-var-derived values before comparison.","After a version upgrade that renames modes, add a migration step that rewrites the old value.","Default to a known mode rather than passing undefined."],"tags":["scheduler","configuration","validation","enum"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}