{"record":{"id":"e5821c3415e6b42a","repo":"GoogleChrome/lighthouse","slug":"missing-metric-metricname","errorCode":null,"errorMessage":"missing metric ${metricName}","messagePattern":"missing metric (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/scripts/lantern/assert-baseline-lantern-values-unchanged.js","lineNumber":40,"sourceCode":"if (!fs.existsSync(HEAD_PATH) || !fs.existsSync(BASELINE_PATH)) {\n  throw new Error('Usage $0 <computed file>');\n}\n\nconst computedResults = readJson(HEAD_PATH);\nconst expectedResults = readJson(BASELINE_PATH);\n\n/** @type {Array<{url: string, maxDiff: number, diffsForSite: Array<DiffForSite>}>} */\nconst diffs = [];\nfor (const entry of computedResults.sites) {\n  // @ts-expect-error - over-aggressive implicit any on candidate\n  const expectedLantern = expectedResults.sites.find(candidate => entry.url === candidate.url);\n  const actualLantern = entry.lantern;\n\n  let maxDiff = 0;\n  /** @type {DiffForSite[]} */\n  const diffsForSite = [];\n  Object.keys(actualLantern).forEach(metricName => {\n    if (!(metricName in expectedLantern)) throw new Error(`missing metric ${metricName}`);\n\n    const actual = Math.round(actualLantern[metricName]);\n    const expected = Math.round(expectedLantern[metricName]);\n    const diff = actual - expected;\n    if (Math.abs(diff) > 0) {\n      maxDiff = Math.max(maxDiff, Math.abs(diff));\n      diffsForSite.push({metricName, actual, expected, diff});\n    }\n  });\n\n  if (maxDiff > 0) diffs.push({url: entry.url, maxDiff, diffsForSite});\n}\n\nif (diffs.length) {\n  console.log(`❌  FAIL    ${diffs.length} change(s) between expected and computed!\\n`);\n\n  diffs.sort((a, b) => b.maxDiff - a.maxDiff).forEach(site => {\n    console.log(chalk.magenta(site.url));","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/GoogleChrome/lighthouse/blob/9515cd4e58ebed69f78742d932b501c2cab8ad8f/core/scripts/lantern/assert-baseline-lantern-values-unchanged.js#L22-L58","documentation":"Thrown during baseline comparison when a metric present in the computed (HEAD) Lantern results is not found in the expected baseline results for a given site. The comparison iterates actualLantern's keys and requires every one to exist in expectedLantern.","triggerScenarios":"A new metric was added to the Lantern output but the baseline file hasn't been regenerated to include it; the baseline was generated from an older version.","commonSituations":"Adding a new metric to the Lantern collector without updating the baseline; using a stale baseline from a different branch.","solutions":["Regenerate the baseline file (`npm run lantern:update-baseline` or equivalent) so it includes all current metrics.","If the metric is intentionally new and the baseline is correct, update the baseline to include the new metric.","Verify both HEAD and baseline were generated from the same set of sites/metrics."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function assertMetricsExist(actualLantern, expectedLantern) {\n  for (const metricName of Object.keys(actualLantern)) {\n    if (!(metricName in expectedLantern)) {\n      throw new Error(`missing metric ${metricName}`);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Regenerate the baseline whenever a new metric is added to Lantern output.","Keep HEAD and baseline generation on the same metric set.","Run baseline update as part of the PR that adds a new metric."],"tags":["lantern","baseline","metrics","assertion"],"backgroundTag":null,"analyzedSha":"9515cd4e58ebed69f78742d932b501c2cab8ad8f","analyzedAt":"2026-08-13T06:28:10.346Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}