n8n-io/n8n · critical · CorruptStorageRowError

scheduled_job ${job.id} has unknown misfire policy '${String

Error message

scheduled_job ${job.id} has unknown misfire policy '${String(exhaustive)}'

What it means

Thrown by the misfire-policy switch when job.misfirePolicy is neither Coalesce nor Skip. The `const exhaustive: never = job.misfirePolicy` pattern turns unhandled enum members into a compile-time error AND a runtime CorruptStorageRowError. The comment names the real cause: a row written by a newer scheduler version (with a new policy) read back after a rollback.

Source

Thrown at packages/@n8n/scheduler/src/core/materializer/misfire.ts:55

	const behind = due.filter((occurrence) => occurrence.getTime() <= now.getTime());
	const ahead = due.slice(behind.length);
	if (behind.length === 0 || behind[0].getTime() > graceDeadline) {
		return { occurrences: due, catchUpAt: null };
	}

	switch (job.misfirePolicy) {
		case ScheduledJobMisfirePolicy.Coalesce: {
			if (truncated && ahead.length === 0) return { occurrences: [], catchUpAt: null };
			const catchUpAt = behind[behind.length - 1];
			return { occurrences: [catchUpAt, ...ahead], catchUpAt };
		}
		case ScheduledJobMisfirePolicy.Skip:
			return { occurrences: ahead, catchUpAt: null };
		default: {
			// A policy this version does not know, e.g. a row written by a newer instance
			// and read back after a rollback.
			const exhaustive: never = job.misfirePolicy;
			throw new CorruptStorageRowError(
				`scheduled_job ${job.id} has unknown misfire policy '${String(exhaustive)}'`,
			);
		}
	}
}

/** Groups a pass's discarded occurrences by the task type and policy that discarded them. */
export function countMisfires(planned: PlannedJob[]): MisfireCount[] {
	const grouped = planned
		.filter(({ plan }) => plan.skippedOccurrences > 0)
		.reduce((groups, { job, plan }) => {
			const key = `${job.taskType}:${job.misfirePolicy}`;
			return groups.set(key, {
				taskType: job.taskType,
				policy: job.misfirePolicy,
				discarded: (groups.get(key)?.discarded ?? 0) + plan.skippedOccurrences,
			});
		}, new Map<string, MisfireCount>());

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Restore all scheduler instances to the same version so the enum matches the rows.
  2. If the row is genuinely corrupt, update the misfire_policy column to a known value (Coalesce or Skip) for the offending job id.
  3. Forward-migrate the schema rather than rolling back, so new policy values stay understood.
  4. Pin a single scheduler version across the fleet; never run mixed versions against one DB.

Example fix

-- before: row has a policy byte the running build does not know
UPDATE scheduled_job SET misfire_policy = 99 WHERE id = 'job-42';

-- after: rewrite to a policy the current enum defines (Coalesce = the safe default)
UPDATE scheduled_job SET misfire_policy = 0 WHERE id = 'job-42';
Defensive patterns

Strategy: try-catch

Validate before calling

import { ScheduledJobMisfirePolicy } from '../../types';

const KNOWN_POLICIES = new Set<number>(Object.values(ScheduledJobMisfirePolicy));

function rowHasKnownPolicy(row: { misfirePolicy: number }): boolean {
  return KNOWN_POLICIES.has(row.misfirePolicy);
}

Type guard

function isKnownMisfirePolicy(v: unknown): v is ScheduledJobMisfirePolicy {
  return typeof v === 'number' && KNOWN_POLICIES.has(v);
}

Try / catch

try {
  applyMisfirePolicy(job);
} catch (e) {
  if (e instanceof CorruptStorageRowError && /unknown misfire policy/.test(e.message)) {
    // Do NOT silently retry - quarantine the row and alert.
    logger.error('Quarantining scheduled_job with unknown misfire policy', { jobId: job.id, policy: job.misfirePolicy });
    await quarantineJob(job.id);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: A scheduled_job row in the DB carries a misfire-policy byte/string that this build's ScheduledJobMisfirePolicy enum does not define. Happens after a forward-then-backward version migration, a manual DB edit, or a corrupt row.

Common situations: Rolling back a scheduler release that introduced a new policy (e.g. 'FireAndForget'); restoring a DB backup from a newer version onto an older instance; concurrent instances running different scheduler versions against the same DB.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/28be03d344b505c4. Report an issue: GitHub.