{"record":{"id":"07c688ced9957ff3","repo":"JuliusBrussee/caveman","slug":"label-key-has-no-pricing-block","errorCode":null,"errorMessage":"${label}: ${key} has no pricing block","messagePattern":"(.+?): (.+?) has no pricing block","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/generate-agent-catalog.mjs","lineNumber":167,"sourceCode":"    if (!entry.reasons.includes(reason)) entry.reasons.push(reason);\n  };\n  for (const row of rows) {\n    for (const field of [\"provider\", \"model\", \"region\", \"currency\"]) {\n      if (typeof row[field] !== \"string\" || row[field] === \"\") {\n        throw new Error(`${label}: row is missing a string \"${field}\"`);\n      }\n    }\n    const key = `${row.provider}/${row.model}`;\n    if (row.region !== REGION_AGNOSTIC) {\n      skipRegional(key, row.region);\n      continue;\n    }\n    if (row.currency !== REQUIRED_CURRENCY) {\n      skip(key, `priced in ${row.currency}, not ${REQUIRED_CURRENCY}`);\n      continue;\n    }\n    if (row.pricing === null || typeof row.pricing !== \"object\" || Array.isArray(row.pricing)) {\n      throw new Error(`${label}: ${key} has no pricing block`);\n    }\n    const input = rate(row.pricing.input_per_million, `${key}.input_per_million`, label);\n    const output = rate(row.pricing.output_per_million, `${key}.output_per_million`, label);\n    if (input === null || output === null) {\n      skip(key, \"no list price for input or output tokens\");\n      continue;\n    }\n    if (seen.has(key)) throw new Error(`${label}: duplicate region-agnostic row ${key}`);\n    seen.add(key);\n    // reasoning_output_per_million is null on every provider that bills thinking\n    // tokens at the plain output rate. catalogCost subtracts reasoning tokens\n    // out of output before pricing them, so a 0 here would make them free.\n    const reasoning = rate(row.pricing.reasoning_output_per_million, `${key}.reasoning_output_per_million`, label);\n    selected.push({\n      key,\n      price: {\n        inputPerMillion: input,\n        outputPerMillion: output,","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/scripts/generate-agent-catalog.mjs#L149-L185","documentation":"Thrown by selectRows() in scripts/generate-agent-catalog.mjs when a row is region-agnostic (`region: global`) and priced in USD but its `pricing` field is missing, null, an array, or a non-object. Regional rows and non-USD rows are skipped politely with a recorded reason; a global USD row is expected to be carried into the agent catalog, so lacking a pricing block entirely is treated as malformed data.","triggerScenarios":"A row with `region: global` and `currency: USD` but no `pricing:` sub-block (or `pricing: null`, or `pricing:` populated with 4-space dash sequence items making it an array).","commonSituations":"Adding a new global model row and forgetting the pricing block; refactoring the catalog and dropping the nested block; a row that previously was regional flipped to global without adding prices.","solutions":["Add a `pricing:` block to the row with at least `input_per_million` and `output_per_million` (cache/reasoning fields optional, null allowed).","If the row genuinely has no USD global list price, change `region` to a concrete region or `currency` to the real currency so the row is skipped with a reason instead of failing the build.","Re-run the generator to confirm the row is selected or recorded as skipped."],"exampleFix":"# before\n- provider: acme\n  model: acme-1\n  region: global\n  currency: USD\n# after\n- provider: acme\n  model: acme-1\n  region: global\n  currency: USD\n  pricing:\n    input_per_million: 1.5\n    output_per_million: 6","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function hasPricingBlock(row) {\n  return row.pricing !== null && typeof row.pricing === \"object\" && !Array.isArray(row.pricing);\n}","tryCatchPattern":null,"preventionTips":["Any row marked `region: global` + `currency: USD` must carry a pricing block — decide skip vs select consciously.","If a model has no global USD price, set a concrete region or its true currency so it is skipped with a reason."],"tags":["yaml","validation","pricing","catalog"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}