{"record":{"id":"99519bec1ad0966e","repo":"heygen-com/hyperframes","slug":"ambiguous-motion-sidecars-in-projectdir-matc","errorCode":null,"errorMessage":"ambiguous motion sidecars in ${projectDir}: ${matched.join(\", \")} each match a composition — remove the sidecars you do not need, or use one composition per project","messagePattern":"ambiguous motion sidecars in (.+?): (.+?) each match a composition — remove the sidecars you do not need, or use one composition per project","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/motionSpec.ts","lineNumber":126,"sourceCode":"/**\n * Locate a `*.motion.json` sidecar in the project dir. When several exist,\n * prefer the one whose basename matches a composition html file; otherwise\n * take the first alphabetically. Throws when multiple sidecars each match a\n * different composition — the bundler and this resolver would diverge silently.\n * Returns null when none is present.\n */\nexport function findMotionSpec(projectDir: string): string | null {\n  if (!existsSync(projectDir)) return null;\n  const entries = readdirSync(projectDir);\n  const sidecars = entries.filter((name) => name.endsWith(\".motion.json\")).sort();\n  if (!sidecars[0]) return null;\n  if (sidecars.length === 1) return join(projectDir, sidecars[0]);\n  const htmlBases = new Set(\n    entries.filter((name) => name.endsWith(\".html\")).map((name) => basename(name, \".html\")),\n  );\n  const matched = sidecars.filter((name) => htmlBases.has(basename(name, \".motion.json\")));\n  if (matched.length > 1) {\n    throw new Error(\n      `ambiguous motion sidecars in ${projectDir}: ${matched.join(\", \")} each match a composition — remove the sidecars you do not need, or use one composition per project`,\n    );\n  }\n  return join(projectDir, matched[0] ?? sidecars[0]);\n}\n\nexport function readMotionSpec(path: string): MotionSpecParse {\n  let raw: unknown;\n  try {\n    raw = JSON.parse(readFileSync(path, \"utf-8\"));\n  } catch (err) {\n    return { ok: false, errors: [`could not read ${basename(path)}: ${(err as Error).message}`] };\n  }\n  return parseMotionSpec(raw);\n}\n","sourceCodeStart":108,"sourceCodeEnd":142,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/utils/motionSpec.ts#L108-L142","documentation":"findMotionSpec scans the project dir for *.motion.json sidecars. When more than one sidecar exists and more than one of them has a basename matching a *.html composition file (e.g. index.motion.json matches index.html AND intro.motion.json matches intro.html), the resolver cannot pick one deterministically — and the bundler might pick differently, causing silent divergence. The library refuses rather than guessing.","triggerScenarios":"A project directory contains two or more composition HTML files AND two or more matching motion sidecars: index.html + index.motion.json, and intro.html + intro.motion.json. Each sidecar legitimately matches its composition, so neither alphabetical-first fallback is safe.","commonSituations":"User drafted multiple compositions in one project folder (against the one-composition-per-project convention) and added motion specs to each; a refactor merged two single-composition projects into one directory without consolidating; leftover/renamed sidecars from an old composition still matching a renamed HTML file.","solutions":["Remove the motion sidecars you do not need so only one remains, or keep one composition per project directory.","If you genuinely need multiple compositions, split them into separate project directories each with its own index.html + index.motion.json.","Rename or delete stale sidecars that no longer correspond to an active composition."],"exampleFix":"# before: two matching sidecars\nproject/\n  index.html\n  index.motion.json\n  intro.html\n  intro.motion.json   # ambiguous\n# after: one composition per project\nproject-a/index.html + index.motion.json\nproject-b/intro.html + intro.motion.json","handlingStrategy":"validation","validationCode":"import { readdirSync } from 'node:fs';\nimport { basename } from 'node:path';\n\nfunction checkNoAmbiguousMotionSidecars(projectDir: string): void {\n  const entries = readdirSync(projectDir);\n  const sidecars = entries.filter(n => n.endsWith('.motion.json'));\n  const htmlBases = new Set(entries.filter(n => n.endsWith('.html')).map(n => basename(n, '.html')));\n  const matched = sidecars.filter(n => htmlBases.has(basename(n, '.motion.json')));\n  if (matched.length > 1) {\n    throw new Error(`Remove ambiguous sidecars: ${matched.join(', ')}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return runCheckPipeline(project, options);\n} catch (err) {\n  if (err instanceof Error && /ambiguous motion sidecars/.test(err.message)) {\n    // list and ask the user to remove the unneeded sidecar(s)\n    console.error(err.message);\n  } else throw err;\n}","preventionTips":["Keep one composition per project directory (the documented convention).","When merging projects, delete stale *.motion.json sidecars.","Before publishing, scan the dir for multiple *.motion.json files as a preflight."],"tags":["motion-spec","sidecar","configuration","composition","bundling"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}