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
- 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.
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
- 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.
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
- [chromium] Chrome binary unavailable (source=${source}): HYP
- [chromium] Chrome binary unavailable (source=${source}): HYP
- This composition declares data-requires-webgpu, but browser
- Render failed for variant "${variant.label}" (${variant.disp
- [hyperframes lambda] could not find the repo root (no packag
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/99519bec1ad0966e.
Report an issue: GitHub.