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

  1. 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.
  2. 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.
  3. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/13ad63d5b2f9bdba. Report an issue: GitHub.