tinyhumansai/openhuman · error · SpecError

batch_id must be a kebab-case slug

Error message

batch_id must be a kebab-case slug

What it means

Thrown by validateSpec() in scripts/agent-batch/lib.mjs when the top-level batch_id field is present but is not a string matching ^[a-z0-9][a-z0-9-]*$. The slug policy exists because batch_id is used to name tracking artifacts (branches, files, issue titles), so it must be lowercase, start with a letter/digit, and contain only letters, digits, and dashes. SpecError prefixes the message with the JSON path "batch_id" via its `path` argument.

Source

Thrown at scripts/agent-batch/lib.mjs:54

  }
  return json;
}

// Validate a parsed spec. Returns the spec on success, throws SpecError on
// any policy violation. The caller is responsible for printing.
export function validateSpec(spec) {
  if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
    throw new SpecError("spec must be a JSON object");
  }
  for (const key of REQUIRED_TOP) {
    if (!(key in spec))
      throw new SpecError(`missing required top-level field "${key}"`);
  }
  if (
    typeof spec.batch_id !== "string" ||
    !/^[a-z0-9][a-z0-9-]*$/.test(spec.batch_id)
  ) {
    throw new SpecError("batch_id must be a kebab-case slug", "batch_id");
  }
  if (spec.base_repo !== "tinyhumansai/openhuman") {
    throw new SpecError(
      `base_repo must be "tinyhumansai/openhuman" (got "${spec.base_repo}")`,
      "base_repo",
    );
  }
  if (spec.base_branch !== "main") {
    throw new SpecError(
      `base_branch must be "main" (got "${spec.base_branch}")`,
      "base_branch",
    );
  }
  if (!Number.isInteger(spec.tracking_issue) || spec.tracking_issue <= 0) {
    throw new SpecError(
      "tracking_issue must be a positive integer",
      "tracking_issue",
    );

View on GitHub (pinned to a221052e0d)

Solutions

  1. Set batch_id to a lowercase kebab-case slug, e.g. "batch-august-ci"
  2. Verify with node: /^[a-z0-9][a-z0-9-]*$/.test(batchId) before running the tool
  3. If the value comes from user input, normalize it first: s.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-')
  4. Check the error's `.path` field ("batch_id") to confirm which field was rejected

Example fix

// before
{
  "batch_id": "Batch_August_CI",
  ...
}

// after
{
  "batch_id": "batch-august-ci",
  ...
}
Defensive patterns

Strategy: validation

Validate before calling

const slugOk = (v) => typeof v === "string" && /^[a-z0-9][a-z0-9-]*$/.test(v);
if (!slugOk(spec.batch_id)) {
  console.error("batch_id must be kebab-case");
  process.exit(1);
}

Type guard

function isKebabSlug(v) {
  return typeof v === "string" && /^[a-z0-9][a-z0-9-]*$/.test(v);
}

Try / catch

import { SpecError, validateSpec } from "./scripts/agent-batch/lib.mjs";
try {
  validateSpec(spec);
} catch (e) {
  if (e instanceof SpecError) {
    console.error(`spec invalid at ${e.path ?? "<root>"}: ${e.message}`);
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling validateSpec(spec) (directly or via the agent-batch CLI) with a spec whose batch_id is "Batch-August" (uppercase), "batch_01" (underscore), "-august" or "august-" (leading/trailing dash), "" (empty), or a non-string like 2026 or null.

Common situations: Hand-editing a spec JSON after copying an internal ticket name that is CamelCase or snake_case; generating the spec from a template that inserts a title with spaces; a trailing whitespace or newline pasted into the value.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/07aca2c3a268a2e3. Report an issue: GitHub.