remotion-dev/remotion · error
Cannot duplicate effect: not-call-expression
Error message
Cannot duplicate effect: not-call-expression
What it means
After grouping effects to duplicate, duplicateEffects performs a final invariant check that each array slot being cloned is a CallExpression (an effect call like LightLeak({...})). If the slot is null, a spread, conditional, or other node it cannot clone it and throws. This is the second, defensive line after findEffectCallExpression already filtered candidates.
Solutions
- Rewrite the effects array so every entry is a plain effect CallExpression
- Duplicate a different index that points at a call expression
- Skip indices resolving to non-call elements before calling duplicateEffects
- Update the effect via addEffect/delete + add if the source entry is a complex expression
Example fix
// before
effects={[...(getEffects()), LightLeak({})]}
// after
effects={[getEffects()[0], LightLeak({})]} // or plain calls only Defensive patterns
Strategy: validation
Validate before calling
for (const el of arr.elements) {
if (el && el.type !== 'CallExpression') throw new Error('effects array contains a non-call entry');
} Type guard
const isCallElement = (el: (Node | null)[] extends never ? never : Node | null | undefined): el is CallExpression => !!el && el.type === 'CallExpression';
Try / catch
try { await duplicateEffects({input, effects}); } catch (e) { if ((e as Error).message.endsWith('not-call-expression')) { /* normalize the effects array, then retry */ } else throw e; } Prevention
- Write only plain CallExpression entries in effects arrays
- Avoid spreads/conditionals inside effects={[...]
- Lint effect props to enforce call-only arrays
When it happens
Trigger: An effects array containing non-call entries (e.g. {...spread}, cond ? A({}) : B({}), comments/nulls) at an index selected for duplication; a race where the array was mutated between validation and the splice loop.
Common situations: Hand-edited effects props with conditional or spread entries; older source written before strict call-only arrays were enforced; concurrent edits changing the array during the operation.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Cannot split audio from a JSX element with no parent
- Cannot update effect without a JSX source location
- Expected the JSX name attribute to be a string
- Cannot create an undo entry without snapshots
- Cannot delete effect: no effects found
AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18).
Data as JSON: /api/errors/5232758ddabbeecc.
Report an issue: GitHub.
Appendix: source
Thrown at packages/studio-codemods/src/effect-operations.ts:513
};
effectsByAttribute.set(attr, group);
trackOpeningElement({jsx, snapshots});
if (group.indices.has(effectIndex)) {
continue;
}
group.indices.add(effectIndex);
group.effectLabels.push(`${found.callee}()`);
group.logLines.push(
found.call.loc?.start.line ?? jsx.loc?.start.line ?? 1,
);
}
for (const {array, indices} of effectsByAttribute.values()) {
for (const effectIndex of [...indices].sort((a, bValue) => bValue - a)) {
const effect = array.elements[effectIndex];
if (!effect || effect.type !== 'CallExpression') {
throw new Error('Cannot duplicate effect: not-call-expression');
}
array.elements.splice(
effectIndex + 1,
0,
cloneAstValue(effect) as never,
);
}
}
return {
output: getEffectSourceOutput({
ast,
input,
prettierConfigOverride: prettierConfigOverride ?? null,
snapshots,
}),
formatted: true,View on GitHub (pinned to 46a3a6bf13)