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.

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@46a3a6bf13 (2026-08-22). Data as JSON: /api/errors/d176ec63c611990e. Report an issue: GitHub.

Appendix: source

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

			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 46a3a6bf13)