heygen-com/hyperframes · error · Error

Could not persist selector: ${options.selector}

Error message

Could not persist selector: ${options.selector}

What it means

Thrown by applyMediaTreatmentToHtml() in packages/cli/src/commands/media-treatment.ts:466. selectMediaElement successfully found the element via parseHTML (linkedom), but the separate patchElementInHtml writer (from @hyperframes/studio-server/source-mutation) returned matched:false — it could not relocate the element to write the data-hf-color-grading attribute. The read path and write path use different HTML engines, so edge-case HTML can diverge.

Source

Thrown at packages/cli/src/commands/media-treatment.ts:466

  options: ApplyMediaTreatmentOptions,
): ApplyMediaTreatmentResult {
  const { element, selectorIndex, tag } = selectMediaElement(
    source,
    options.selector,
    options.selectorIndex,
  );
  const before = parseStoredGrading(element.getAttribute(HF_COLOR_GRADING_ATTR));

  const value = options.clear ? null : serializeGradingPatch(before, options.grading);

  const changed = element.getAttribute(HF_COLOR_GRADING_ATTR) !== value;
  const after = parseStoredGrading(value);
  if (!changed) return { html: source, changed: false, tag, value, before, after };

  const patched = patchElementInHtml(source, { selector: options.selector, selectorIndex }, [
    { type: "attribute", property: HF_COLOR_GRADING_ATTR, value },
  ]);
  if (!patched.matched) throw new Error(`Could not persist selector: ${options.selector}`);
  return { html: patched.html, changed: true, tag, value, before, after };
}

function parseSelectorIndex(raw: string | undefined): number | undefined {
  if (raw === undefined) return undefined;
  const value = Number(raw);
  if (!Number.isInteger(value) || value < 0) {
    throw new Error("--selector-index must be a non-negative integer");
  }
  return value;
}

function mediaSourceForElement(element: Element): string {
  const src =
    element.getAttribute("src") ??
    (element.tagName.toLowerCase() === "video"
      ? element.querySelector("source")?.getAttribute("src")
      : null);

View on GitHub (pinned to c2996c8626)

Solutions

  1. Simplify the HTML immediately around the target element (remove comments/conditional blocks/temporary wrapper templates) and retry.
  2. Use a more specific selector so the patcher has less ambiguity.
  3. Run `hyperframes lint` on the composition — structural HTML issues are often flagged there too.
  4. If the HTML is valid and the error persists, it's a parser-divergence bug; report it with the minimal HTML repro.
Defensive patterns

Strategy: try-catch

Validate before calling

import { patchElementInHtml } from '@hyperframes/studio-server/source-mutation';

function canPersist(source: string, selector: string, index: number): boolean {
  const result = patchElementInHtml(source, { selector, selectorIndex: index }, []);
  return result.matched;
}

Try / catch

try {
  applyMediaTreatmentToHtml(source, opts);
} catch (error) {
  if (/Could not persist selector/.test(String(error))) {
    // simplify surrounding HTML, use a more specific selector, then retry
    throw new Error('Read/write parser divergence — simplify the HTML around the target and retry');
  }
  throw error;
}

Prevention

When it happens

Trigger: The composition HTML around the target element is structured in a way patchElementInHtml can't match: malformed HTML, unusual template/comment/CDATA wrapping, self-closing edge cases, or elements linkedom parses differently from the patcher.

Common situations: HTML with unusual comment placement, conditional comments, <template> fragments, or partial markup that the two engines normalize differently; very large/deeply nested files.

Related errors


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