heygen-com/hyperframes · error

ambiguous motion sidecars in ${projectDir}: ${matched.join("

Error message

ambiguous motion sidecars in ${projectDir}: ${matched.join(", ")} each match a composition — remove the sidecars you do not need, or use one composition per project

What it means

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.

Source

Thrown at packages/cli/src/utils/motionSpec.ts:126

/**
 * Locate a `*.motion.json` sidecar in the project dir. When several exist,
 * prefer the one whose basename matches a composition html file; otherwise
 * take the first alphabetically. Throws when multiple sidecars each match a
 * different composition — the bundler and this resolver would diverge silently.
 * Returns null when none is present.
 */
export function findMotionSpec(projectDir: string): string | null {
  if (!existsSync(projectDir)) return null;
  const entries = readdirSync(projectDir);
  const sidecars = entries.filter((name) => name.endsWith(".motion.json")).sort();
  if (!sidecars[0]) return null;
  if (sidecars.length === 1) return join(projectDir, sidecars[0]);
  const htmlBases = new Set(
    entries.filter((name) => name.endsWith(".html")).map((name) => basename(name, ".html")),
  );
  const matched = sidecars.filter((name) => htmlBases.has(basename(name, ".motion.json")));
  if (matched.length > 1) {
    throw new Error(
      `ambiguous motion sidecars in ${projectDir}: ${matched.join(", ")} each match a composition — remove the sidecars you do not need, or use one composition per project`,
    );
  }
  return join(projectDir, matched[0] ?? sidecars[0]);
}

export function readMotionSpec(path: string): MotionSpecParse {
  let raw: unknown;
  try {
    raw = JSON.parse(readFileSync(path, "utf-8"));
  } catch (err) {
    return { ok: false, errors: [`could not read ${basename(path)}: ${(err as Error).message}`] };
  }
  return parseMotionSpec(raw);
}

View on GitHub (pinned to c2996c8626)

Solutions

  1. Remove the motion sidecars you do not need so only one remains, or keep one composition per project directory.
  2. If you genuinely need multiple compositions, split them into separate project directories each with its own index.html + index.motion.json.
  3. Rename or delete stale sidecars that no longer correspond to an active composition.

Example fix

# before: two matching sidecars
project/
  index.html
  index.motion.json
  intro.html
  intro.motion.json   # ambiguous
# after: one composition per project
project-a/index.html + index.motion.json
project-b/intro.html + intro.motion.json
Defensive patterns

Strategy: validation

Validate before calling

import { readdirSync } from 'node:fs';
import { basename } from 'node:path';

function checkNoAmbiguousMotionSidecars(projectDir: string): void {
  const entries = readdirSync(projectDir);
  const sidecars = entries.filter(n => n.endsWith('.motion.json'));
  const htmlBases = new Set(entries.filter(n => n.endsWith('.html')).map(n => basename(n, '.html')));
  const matched = sidecars.filter(n => htmlBases.has(basename(n, '.motion.json')));
  if (matched.length > 1) {
    throw new Error(`Remove ambiguous sidecars: ${matched.join(', ')}`);
  }
}

Try / catch

try {
  return runCheckPipeline(project, options);
} catch (err) {
  if (err instanceof Error && /ambiguous motion sidecars/.test(err.message)) {
    // list and ask the user to remove the unneeded sidecar(s)
    console.error(err.message);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/99519bec1ad0966e. Report an issue: GitHub.