{"record":{"id":"5232758ddabbeecc","repo":"remotion-dev/remotion","slug":"cannot-duplicate-effect-not-call-expression","errorCode":null,"errorMessage":"Cannot duplicate effect: not-call-expression","messagePattern":"Cannot duplicate effect: not-call-expression","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/studio-codemods/src/effect-operations.ts","lineNumber":513,"sourceCode":"\t\t\t};\n\t\t\teffectsByAttribute.set(attr, group);\n\t\t\ttrackOpeningElement({jsx, snapshots});\n\t\t\tif (group.indices.has(effectIndex)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tgroup.indices.add(effectIndex);\n\t\t\tgroup.effectLabels.push(`${found.callee}()`);\n\t\t\tgroup.logLines.push(\n\t\t\t\tfound.call.loc?.start.line ?? jsx.loc?.start.line ?? 1,\n\t\t\t);\n\t\t}\n\n\t\tfor (const {array, indices} of effectsByAttribute.values()) {\n\t\t\tfor (const effectIndex of [...indices].sort((a, bValue) => bValue - a)) {\n\t\t\t\tconst effect = array.elements[effectIndex];\n\t\t\t\tif (!effect || effect.type !== 'CallExpression') {\n\t\t\t\t\tthrow new Error('Cannot duplicate effect: not-call-expression');\n\t\t\t\t}\n\n\t\t\t\tarray.elements.splice(\n\t\t\t\t\teffectIndex + 1,\n\t\t\t\t\t0,\n\t\t\t\t\tcloneAstValue(effect) as never,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\toutput: getEffectSourceOutput({\n\t\t\t\tast,\n\t\t\t\tinput,\n\t\t\t\tprettierConfigOverride: prettierConfigOverride ?? null,\n\t\t\t\tsnapshots,\n\t\t\t}),\n\t\t\tformatted: true,","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/remotion-dev/remotion/blob/46a3a6bf13ca367376ff18bbb06dae2e87e0d068/packages/studio-codemods/src/effect-operations.ts#L495-L531","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\neffects={[...(getEffects()), LightLeak({})]}\n// after\neffects={[getEffects()[0], LightLeak({})]} // or plain calls only","handlingStrategy":"validation","validationCode":"for (const el of arr.elements) {\n  if (el && el.type !== 'CallExpression') throw new Error('effects array contains a non-call entry');\n}","typeGuard":"const isCallElement = (el: (Node | null)[] extends never ? never : Node | null | undefined): el is CallExpression => !!el && el.type === 'CallExpression';","tryCatchPattern":"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; }","preventionTips":["Write only plain CallExpression entries in effects arrays","Avoid spreads/conditionals inside effects={[...]","Lint effect props to enforce call-only arrays"],"tags":["effects","ast","invariant","unsupported-entry"],"backgroundTag":"unsupported-operation","analyzedSha":"46a3a6bf13ca367376ff18bbb06dae2e87e0d068","analyzedAt":"2026-09-18T00:12:20.256Z","contentChangedAt":"2026-09-18T00:12:20.256Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}