{"record":{"id":"e56ae8ceea9ffafc","repo":"heygen-com/hyperframes","slug":"could-not-parse-grades-json-normalizeerrormessa","errorCode":null,"errorMessage":"Could not parse grades JSON: ${normalizeErrorMessage(err)}","messagePattern":"Could not parse grades JSON: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/grade-compare.ts","lineNumber":217,"sourceCode":"  }\n  if (isRecord(lut)) {\n    const nextLut = cloneRecord(lut);\n    nextLut.src = src;\n    next.lut = nextLut;\n  }\n  return next;\n}\n\nexport function parseGradesFile(filePath: string): GradeCompareCell[] {\n  if (!existsSync(filePath)) {\n    throw new Error(`Grades file not found: ${filePath}`);\n  }\n\n  let parsed: unknown;\n  try {\n    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);","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/commands/grade-compare.ts#L199-L235","documentation":"Thrown by parseGradesFile() when JSON.parse throws on the file contents. The inner error message is normalized and appended. The parser uses strict JSON — no comments, no trailing commas, no unquoted keys, no JSON5/JSONC.","triggerScenarios":"A trailing comma; unquoted keys; // or /* */ comments; single-quoted strings; smart/curly quotes pasted from a doc; a truncated file; a UTF-8 BOM that trips strict parsing.","commonSituations":"Hand-editing the grades file and treating it as JSONC; pasting an example that used typographic quotes; an editor that strips the final brace.","solutions":["Validate the file with a JSON linter or `node -e 'JSON.parse(require(\"fs\").readFileSync(\"grades.json\",\"utf8\"))'`","Use strict JSON: double quotes, no comments, no trailing commas","Re-save the file as plain UTF-8 without BOM"],"exampleFix":"// before (unquoted keys, trailing comma)\n{\n  label: \"warm\",\n  grading: {},\n}\n// after\n[\n  { \"label\": \"warm\", \"grading\": {} }\n]","handlingStrategy":"validation","validationCode":"try {\n  JSON.parse(readFileSync(filePath, 'utf-8'));\n} catch (err) {\n  throw new Error(`grades JSON is invalid: ${(err as Error).message}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  parsed = JSON.parse(readFileSync(filePath, 'utf-8'));\n} catch (err) {\n  // Surface a targeted message with the file path and the parser's position.\n  throw new Error(`Could not parse grades JSON in ${filePath}: ${(err as Error).message}`);\n}","preventionTips":["Use strict JSON: double quotes, no comments, no trailing commas","Run the file through a JSON linter or `node -e JSON.parse(...)` before the command","Save as plain UTF-8 without a BOM"],"tags":["grade-compare","json","parse-error","validation"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}