remotion-dev/remotion · error · Error

No keyframe changes were specified

Error message

No keyframe changes were specified

What it means

mutateKeyframesInProject powers the sequence and effect keyframe operations (move/add/delete keyframes). It refuses no-op calls: when both sequenceMutations and effectMutations are empty arrays it throws instead of committing an empty change and polluting the undo stack.

Source

Thrown at packages/browser-studio/src/browser-studio-operations.ts:513

			onProjectChange(project);
			refreshDefaultPropsSubscriptions();
			if (!metadata.skipSequencePropsUpdate) {
				refreshSequencePropsSubscriptions();
			}
		},
	});

	const mutateKeyframesInProject = async ({
		project,
		sequenceMutations,
		effectMutations,
	}: {
		project: VirtualProject;
		sequenceMutations: SequenceKeyframeMutation[];
		effectMutations: EffectKeyframeMutation[];
	}) => {
		if (sequenceMutations.length === 0 && effectMutations.length === 0) {
			throw new Error('No keyframe changes were specified');
		}

		const mergeUpdates = <T extends SequenceKeyframeUpdate>(updates: T[]) => {
			const merged: T[] = [];
			for (const update of updates) {
				const existingMove = merged.find(
					(candidate) =>
						candidate.key === update.key &&
						candidate.operation.type === 'move' &&
						update.operation.type === 'move',
				);
				if (
					existingMove?.operation.type === 'move' &&
					update.operation.type === 'move'
				) {
					existingMove.operation.moves.push(...update.operation.moves);
				} else {
					merged.push(update);

View on GitHub (pinned to b2f4e34732)

Solutions

  1. Skip the operation call when both mutation arrays are empty — treat it as a no-op.
  2. Fix the upstream filtering so intended keyframe changes survive into the request.
  3. Keep UI state unchanged on empty batches instead of surfacing the error to the user.

Example fix

// before
await saveKeyframes({sequenceMutations: filtered, effectMutations: []});

// after
if (filtered.length > 0) {
  await saveKeyframes({sequenceMutations: filtered, effectMutations: []});
}
Defensive patterns

Strategy: validation

Validate before calling

const hasChanges =
  sequenceMutations.length > 0 || effectMutations.length > 0;
if (hasChanges) {
  await commitKeyframes({sequenceMutations, effectMutations});
}

Prevention

When it happens

Trigger: Invoking a keyframe mutation operation (via commitKeyframeMutations consumers such as updateSequenceKeyframes-style flows) after client-side filtering removed every requested change, leaving zero mutations in both arrays.

Common situations: Drag handlers that end without an effective change; batching logic that de-duplicates updates and accidentally empties the batch; gesture code that always calls save even when nothing moved.

Related errors


AI-assisted analysis of remotion-dev/remotion@b2f4e34732 (2026-08-22). Data as JSON: /api/errors/d176ec63c611990e. Report an issue: GitHub.