JuliusBrussee/caveman · error
${label}:${lineNo}: cannot read "${text}" as a "key: value"
Error message
${label}:${lineNo}: cannot read "${text}" as a "key: value" pair What it means
Thrown by the purpose-built flat-YAML parser in scripts/generate-agent-catalog.mjs (splitKeyValue) when a catalog line does not match the strict `key: value` grammar `/^([A-Za-z_][A-Za-z0-9_]*):(?: (.*))?$/`. The generator intentionally avoids a full YAML library and only accepts identifier-style keys with at most one space after the colon; anything it does not recognize is a hard error, it never guesses. The message includes the catalog label and 1-based line number so the offending line can be fixed directly.
Source
Thrown at scripts/generate-agent-catalog.mjs:113
if (Array.isArray(row[blockKey]) || typeof row[blockKey] !== "object") {
throw new Error(`${label}:${lineNo}: "${blockKey}" mixes map and sequence entries`);
}
const entry = splitKeyValue(rest, label, lineNo);
if (entry.value === undefined) throw new Error(`${label}:${lineNo}: nesting deeper than two levels is not supported`);
if (Object.prototype.hasOwnProperty.call(row[blockKey], entry.key)) {
throw new Error(`${label}:${lineNo}: duplicate key "${blockKey}.${entry.key}"`);
}
row[blockKey][entry.key] = entry.value;
continue;
}
throw new Error(`${label}:${lineNo}: unexpected indent of ${indent} spaces`);
}
return rows;
}
function splitKeyValue(text, label, lineNo) {
const match = /^([A-Za-z_][A-Za-z0-9_]*):(?: (.*))?$/.exec(text);
if (match === null) throw new Error(`${label}:${lineNo}: cannot read "${text}" as a "key: value" pair`);
return { key: match[1], value: match[2] === undefined ? undefined : scalar(match[2], label, lineNo) };
}
function scalar(text, label, lineNo) {
if (text === "") throw new Error(`${label}:${lineNo}: empty value`);
if (text === "null") return null;
if (text === "true") return true;
if (text === "false") return false;
if (/^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$/.test(text)) {
const value = Number(text);
if (!Number.isFinite(value)) throw new Error(`${label}:${lineNo}: "${text}" is not a finite number`);
return value;
}
return text;
}
/** Selects the priced, region-agnostic, USD rows the agent catalog can honestly carry. */
export function selectRows(rows, label = CATALOG_LABEL) {View on GitHub (pinned to 27d5a3981a)
Solutions
- Open the file and line named in the message and rewrite the line as `key: value` where key matches [A-Za-z_][A-Za-z0-9_]* (use snake_case) and there is exactly one space after the colon.
- Remove YAML features the mini-parser does not support (quoted keys, anchors, flow maps like {a: 1}); keep the flat two-level `- key:` row shape documented at the top of the script.
- Re-run `node scripts/generate-agent-catalog.mjs` to confirm the catalog now parses and regenerates public/agent/src/catalog.ts.
Example fix
# before (catalog current.yaml) input-per-million: 0.25 # after input_per_million: 0.25
Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate catalog lines against the generator's grammar before running it.
import { readFileSync } from "node:fs";
const KEY_VALUE = /^([A-Za-z_][A-Za-z0-9_]*):(?: (.*))?$/;
function checkCatalog(text) {
const bad = [];
text.split("\n").forEach((line, i) => {
const l = line.replace(/\s+$/, "");
if (l === "" || /^ *#/.test(l)) return;
const rest = /^( *)(- )?(.*)$/.exec(l)[3];
if (rest.includes(":") && !KEY_VALUE.test(rest) && !rest.startsWith("- ")) bad.push(i + 1);
});
return bad;
} Try / catch
try {
execFileSync("node", ["scripts/generate-agent-catalog.mjs"]);
} catch (error) {
// messages are `${label}:${lineNo}: ...` — split on the first colon pair
const m = /^(.*):(\d+): (.*)$/.exec(error.stderr?.toString() ?? error.message);
if (m) reportCatalogIssue(m[1], Number(m[2]), m[3]);
else throw error;
} Prevention
- Keep catalog keys snake_case; provider docs' hyphenated names must be translated.
- Run the generator locally right after editing current.yaml instead of waiting for the drift test in CI.
- Enable an editor lint that flags trailing whitespace in YAML files.
When it happens
Trigger: A line inside a row block like `input-per-million: 0.25` (hyphen in key), `my.key: 1` (dot in key), `foo :bar` or `foo:bar` (wrong spacing around the colon), a key starting with a digit, or a stray prose line where a `key: value` entry was expected. Any of these makes the regex exec return null and this error fires.
Common situations: Hand-editing public/shared/provider-catalog/catalog/current.yaml after copying keys from provider pricing docs (which use hyphens or dots); pasting rows from a richer YAML file that uses anchors, quotes, or flow syntax the mini-parser does not support; CI failing on catalog.drift.runtime.mjs after an edit and re-running the generator.
Related errors
- ${label}:${lineNo}: empty value
- ${label}:${lineNo}: expected a top-level "- provider: ..." r
- ${label}:${lineNo}: row must start with an inline "provider"
- ${label}:${lineNo}: indented line before any row
- ${label}:${lineNo}: "${text}" is not a finite number
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/13ad63d5b2f9bdba.
Report an issue: GitHub.