heygen-com/hyperframes · error · Error

At least one grade candidate is required

Error message

At least one grade candidate is required

What it means

Thrown by the grade-compare command's run() after parsing grades/luts and getting back an empty cells array. This is distinct from the lower-level buildGradeCompareHtml guard (error 144): it fires at the command layer, after parseGradesFile or resolveLutCells returned `[]`. In practice parseGradesFile cannot return empty (a JSON `[]` array passes the Array.isArray check and the map yields nothing), and resolveLutCells rejects empty input — so this is the guard that catches an empty `[]` grades file.

Source

Thrown at packages/cli/src/commands/grade-compare.ts:638

  async run({ args }) {
    const jsonRequested = args.json === true;
    let preparedDir: string | null = null;
    try {
      const parsed = parseGradeCompareArgs({
        for: args.for,
        grades: args.grades,
        luts: args.luts,
        project: args.project,
        out: args.out,
        json: args.json,
        timeout: args.timeout,
      });
      let cells =
        parsed.source.kind === "grades"
          ? parseGradesFile(parsed.source.path)
          : resolveLutCells(parsed.source.value);
      if (cells.length === 0) {
        throw new Error("At least one grade candidate is required");
      }
      const capResult = capCandidateCells(cells);
      cells = capResult.cells;
      warnInactiveGradingCells(cells);
      if (args.baseline !== false) {
        cells = prependBaselineCell(cells);
      }

      if (!parsed.json) {
        console.log(
          `${c.accent("◆")}  Rendering ${cells.length} grade candidates from ${c.accent(basename(parsed.framePath))}`,
        );
      }

      const frame = await loadReferenceFrame(parsed.framePath);
      const prepared = await prepareGradeCompareTempProject({
        projectDir: parsed.projectDir,
        framePath: parsed.framePath,

View on GitHub (pinned to c2996c8626)

Solutions

  1. Add at least one `{ label, grading }` entry to grades.json.
  2. If a script generates the file, ensure it errors upstream when it would emit zero candidates rather than writing `[]`.
  3. Switch to `--luts` with at least one .cube path if you have no inline grades to define.

Example fix

// before — grades.json
[]
// after
[{"label":"Warm","grading":{"temperature":0.2}}]
Defensive patterns

Strategy: validation

Validate before calling

// Reject an empty grades array before invoking grade-compare
import { readFileSync } from "node:fs";
function assertNonEmptyGrades(path: string): void {
  const parsed = JSON.parse(readFileSync(path, "utf-8"));
  if (!Array.isArray(parsed) || parsed.length === 0) {
    throw new Error("grades file must be a non-empty array");
  }
}

Type guard

function isNonEmptyArray(v: unknown): v is unknown[] {
  return Array.isArray(v) && v.length > 0;
}

Try / catch

try {
  // run grade-compare
} catch (err) {
  if (/At least one grade candidate/.test((err as Error).message))) {
    // add an entry to grades.json or switch to --luts
  }
}

Prevention

When it happens

Trigger: Supplying `--grades grades.json` where grades.json is exactly `[]` (a valid JSON array with zero entries). The file parses, passes the array check, the map produces no cells, and this guard rejects it.

Common situations: A grades JSON templated/generated by a script that emitted an empty array when no looks matched a filter; hand-editing that stripped all entries; a CI step producing an empty manifest.

Related errors


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