{"record":{"id":"3e9980f4b5c8831c","repo":"heygen-com/hyperframes","slug":"grade-entry-label-must-include-a-grading-valu","errorCode":null,"errorMessage":"Grade entry \"${label}\" must include a grading value","messagePattern":"Grade entry \"(.+?)\" must include a grading value","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/grade-compare.ts","lineNumber":233,"sourceCode":"    parsed = JSON.parse(readFileSync(filePath, \"utf-8\"));\n  } catch (err) {\n    throw new Error(`Could not parse grades JSON: ${normalizeErrorMessage(err)}`);\n  }\n\n  if (!Array.isArray(parsed)) {\n    throw new Error(\"Grades file must be a JSON array of { label, grading } objects\");\n  }\n\n  return parsed.map((entry, index) => {\n    if (!isRecord(entry)) {\n      throw new Error(`Grade entry ${index + 1} must be an object with label and grading`);\n    }\n    const label = entry.label;\n    if (typeof label !== \"string\" || !label.trim()) {\n      throw new Error(`Grade entry ${index + 1} must have a non-empty string label`);\n    }\n    if (!hasOwn(entry, \"grading\")) {\n      throw new Error(`Grade entry \"${label}\" must include a grading value`);\n    }\n    return validateCell(label, entry.grading);\n  });\n}\n\nexport function resolveLutCells(luts: string): GradeCompareCell[] {\n  const paths = luts\n    .split(\",\")\n    .map((part) => part.trim())\n    .filter(Boolean);\n  if (paths.length === 0) {\n    throw new Error(\"--luts must include at least one LUT path\");\n  }\n  return paths.map((lutPath) =>\n    validateCell(basename(lutPath, extname(lutPath)), { lut: { src: lutPath } }),\n  );\n}\n","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/commands/grade-compare.ts#L215-L251","documentation":"Thrown by parseGradesFile when a grades-JSON array entry has a valid `label` string but is missing the `grading` key. Each entry MUST be shaped `{ label, grading }`; the `grading` value is what feeds the color-grading shader, so an entry without it carries nothing to render. The check uses hasOwn, so an explicit `\"grading\": undefined` also trips it.","triggerScenarios":"Running `hyperframes grade-compare --grades grades.json` where at least one array element looks like `{\"label\":\"Warm\"}` (no `grading` key). Also triggered by programmatic callers of the exported parseGradesFile with the same malformed input.","commonSituations":"Hand-authoring a grades JSON and forgetting the field; renaming `grading` to `grade`/`look`/`preset`; schema drift after a HyperFrames upgrade that renamed the key; copy-pasting a partial object.","solutions":["Open grades.json and add a `grading` object to the entry named in the message, e.g. `{\"label\":\"Warm\",\"grading\":{\"brightness\":0.1}}`.","If unsure of the grading shape, run `hyperframes grade-compare --for frame.png --luts x.cube` once to see a valid cell, or consult the color-grading docs for the accepted keys.","Lint the file before running: every top-level array element must own both `label` (non-empty string) and `grading`."],"exampleFix":"// before — grades.json\n[{\"label\":\"Warm\"}]\n// after\n[{\"label\":\"Warm\",\"grading\":{\"brightness\":0.1,\"contrast\":1.1}}]","handlingStrategy":"validation","validationCode":"// Validate a grades file before calling parseGradesFile\nimport { readFileSync } from \"node:fs\";\nfunction validateGradesFile(path: string): void {\n  const parsed = JSON.parse(readFileSync(path, \"utf-8\"));\n  if (!Array.isArray(parsed)) throw new Error(\"grades file must be a JSON array\");\n  for (let i = 0; i < parsed.length; i++) {\n    const e = parsed[i];\n    if (typeof e !== \"object\" || e === null || Array.isArray(e))\n      throw new Error(`entry ${i + 1} is not an object`);\n    if (typeof e.label !== \"string\" || !e.label.trim())\n      throw new Error(`entry ${i + 1} lacks a non-empty label`);\n    if (!Object.prototype.hasOwnProperty.call(e, \"grading\"))\n      throw new Error(`entry ${i + 1} (\"${e.label}\") lacks grading`);\n  }\n}","typeGuard":"function isGradeEntry(v: unknown): v is { label: string; grading: unknown } {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v)\n    && typeof (v as any).label === \"string\" && (v as any).label.trim() !== \"\"\n    && Object.prototype.hasOwnProperty.call(v, \"grading\");\n}","tryCatchPattern":"try {\n  const cells = parseGradesFile(path);\n} catch (err) {\n  // Surface the message verbatim — it already names the offending entry.\n  console.error(String((err as Error).message));\n  process.exit(1);\n}","preventionTips":["Lint grades.json with a JSON Schema requiring both label and grading on every array item.","Use isGradeEntry as a row-level filter in a script that generates the file.","Run `hyperframes grade-compare --help` to re-confirm the { label, grading } contract after upgrades."],"tags":["cli","input-validation","json","color-grading","grade-compare"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}