JuliusBrussee/caveman · error

${label}:${lineNo}: unexpected indent of ${indent} spaces

Error message

${label}:${lineNo}: unexpected indent of ${indent} spaces

What it means

Thrown by the catalog parser when a non-empty, non-comment line's leading indent is neither 0, 2, nor 4 spaces. The parser's world model has exactly three levels (row dash line, row properties, block contents), so 1, 3, 6, 8 spaces, or any other width is structurally invalid — this includes YAML-legal odd indents and tabs expanded differently than the author assumed.

Source

Thrown at scripts/generate-agent-catalog.mjs:106

      if (dash) {
        if (row[blockKey] === undefined) row[blockKey] = [];
        if (!Array.isArray(row[blockKey])) throw new Error(`${label}:${lineNo}: "${blockKey}" mixes map and sequence entries`);
        row[blockKey].push(rest);
        continue;
      }
      if (row[blockKey] === undefined) row[blockKey] = {};
      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`);

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Re-indent the line named in the error to exactly 0 (row), 2 (row property), or 4 (block content) spaces.
  2. Configure the editor to insert spaces with a 2-space indent for this file and re-run the generator; fix each reported line as it surfaces.
  3. Ensure no tab characters are used for indentation anywhere in the file.

Example fix

# before
- provider: anthropic
   model: claude-4   # 3 spaces

# after
- provider: anthropic
  model: claude-4   # 2 spaces
Defensive patterns

Strategy: validation

Validate before calling

function onlyIndent024(text) {
  return text.split("\n").every((line) => {
    if (line.trim() === "" || /^ *#/.test(line)) return true;
    const indent = /^( *)/.exec(line)[1].length;
    return [0, 2, 4].includes(indent) && !line.startsWith("\t");
  });
}

Try / catch

try {
  parseCatalog(text);
} catch (err) {
  if (/unexpected indent of \d+ spaces/.test(err.message)) {
    // re-indent the named line to exactly 0, 2, or 4 spaces
  } else throw err;
}

Prevention

When it happens

Trigger: Indenting a line with 3 spaces between levels; using 6+ spaces for what was meant as a level-2 or level-4 line; mixing tabs and spaces so the counted indent falls outside {0,2,4}; a list item nested under a list item.

Common situations: Editor configured for a different indent width (e.g. 4-space default) applied inconsistently; copy-paste from a differently-indented file; hand-alignment that visually matches but measures differently.

Related errors


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