{"record":{"id":"260e9d69e3f1f105","repo":"n8n-io/n8n","slug":"jitterratio-must-be-at-least-0-and-below-1-got","errorCode":null,"errorMessage":"jitterRatio must be at least 0 and below 1, got ${lifecycleOptions.jitterRatio}","messagePattern":"jitterRatio must be at least 0 and below 1, got (.+?)","errorType":"validation","errorClass":"InvalidLifecycleOptionsError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/scheduler/src/core/factory.ts","lineNumber":145,"sourceCode":" * retention passes bound to their options, with every incident routed to\n * `onEvent` as a described {@link SchedulerEvent}. `start` runs each pass on\n * its own jittered {@link Loop}; `stop` drains them.\n */\nexport function createScheduler(deps: SchedulerDeps): Scheduler & SchedulerPasses {\n\tconst { hostId, materializerTransaction, taskStore, onEvent } = deps;\n\tconst tracer = deps.tracer ?? noopTracer;\n\tconst metrics = deps.metrics ?? noopMetrics;\n\tconst materializerOptions = withDefaults(DEFAULT_MATERIALIZER_OPTIONS, deps.materializer);\n\tconst executorOptions = withDefaults(DEFAULT_EXECUTOR_OPTIONS, deps.executor);\n\tconst reaperOptions = withDefaults(DEFAULT_REAPER_OPTIONS, deps.reaper);\n\tconst retentionOptions = withDefaults(DEFAULT_RETENTION_OPTIONS, deps.retention);\n\tconst lifecycleOptions = withDefaults(DEFAULT_LIFECYCLE_OPTIONS, deps.lifecycle);\n\tconst clockSkewOptions = withDefaults(DEFAULT_CLOCK_SKEW_OPTIONS, deps.clockSkew);\n\tconst dispatchLagWarnThresholdSeconds =\n\t\tdeps.dispatchLagWarnThresholdSeconds ?? DEFAULT_DISPATCH_LAG_WARN_THRESHOLD_SECONDS;\n\n\tif (!(lifecycleOptions.jitterRatio >= 0 && lifecycleOptions.jitterRatio < 1)) {\n\t\tthrow new InvalidLifecycleOptionsError(\n\t\t\t`jitterRatio must be at least 0 and below 1, got ${lifecycleOptions.jitterRatio}`,\n\t\t);\n\t}\n\n\t// A non-positive or NaN interval collapses to setTimeout's 1ms floor,\n\t// turning the pass into a hot loop against task storage.\n\t// A non-positive or NaN timeout would abandon every pass the moment it starts.\n\tconst durationKeys = [\n\t\t'materializerIntervalSeconds',\n\t\t'executorIntervalSeconds',\n\t\t'reaperIntervalSeconds',\n\t\t'retentionIntervalSeconds',\n\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) {","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/scheduler/src/core/factory.ts#L127-L163","documentation":"Thrown by the scheduler factory (createScheduler) when lifecycleOptions.jitterRatio, after applying DEFAULT_LIFECYCLE_OPTIONS defaults, is not in [0, 1). jitterRatio spreads materializer/executor wake-ups to avoid thundering-herd against task storage; a value >= 1 or negative makes jitter arithmetic produce negative or huge delays.","triggerScenarios":"Calling createScheduler({ lifecycle: { jitterRatio: 1 } }) (boundary exclusive), jitterRatio: -0.1, or NaN (NaN fails both >= 0 and < 1). Also triggered if a config loader coerces a percentage (e.g. 50 for 50%) into 50 instead of 0.5.","commonSituations":"Migrating from a config that expressed jitter as a percentage (0-100) instead of a ratio (0-1); a typo; NaN slipped in from parseFloat(undefined); env var override that drops the decimal point.","solutions":["Set lifecycle.jitterRatio to a number in [0, 1), e.g. 0.25 for +/-25% jitter.","If your config is a percentage, divide by 100 before passing: jitterRatio: pct / 100.","Coerce with Number() and validate with Number.isFinite before calling createScheduler.","Omit jitterRatio to accept the DEFAULT_LIFECYCLE_OPTIONS value."],"exampleFix":"// before\nconst scheduler = createScheduler({ lifecycle: { jitterRatio: 25, ... } }); // throws\n\n// after - normalize percentage to ratio, clamp into range\nconst pct = Number(process.env.SCHED_JITTER_PCT ?? 25);\nconst jitterRatio = Math.min(Math.max(pct / 100, 0), 0.999);\nconst scheduler = createScheduler({ lifecycle: { jitterRatio, ... } });","handlingStrategy":"validation","validationCode":"function normalizeJitter(raw: unknown): number {\n  const n = Number(raw);\n  if (!Number.isFinite(n)) return 0.25; // default fallback\n  // accept either ratio (0..1) or percentage (0..100)\n  const ratio = n > 1 ? n / 100 : n;\n  return Math.min(Math.max(ratio, 0), 0.999);\n}\n\nconst jitterRatio = normalizeJitter(process.env.SCHED_JITTER);\nif (!(jitterRatio >= 0 && jitterRatio < 1)) {\n  throw new Error('jitterRatio out of range');\n}","typeGuard":"function isValidJitterRatio(n: unknown): n is number {\n  return typeof n === 'number' && Number.isFinite(n) && n >= 0 && n < 1;\n}","tryCatchPattern":null,"preventionTips":["Decide upfront whether your config expresses jitter as ratio (0..1) or percentage (0..100) and normalize at the boundary.","Clamp into [0, 0.999] in the config loader so a boundary value of 1 cannot reach createScheduler.","Reject NaN explicitly (Number.isFinite does this).","Document the unit next to the config key."],"tags":["scheduler","configuration","validation","numeric"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}