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
- Set batch_id to a lowercase kebab-case slug, e.g. "batch-august-ci"
- Verify with node: /^[a-z0-9][a-z0-9-]*$/.test(batchId) before running the tool
- If the value comes from user input, normalize it first: s.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-')
- 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
- Generate batch_id from the batch title with slugify (lowercase, dashes) rather than typing it
- Run validateSpec immediately after loadSpec in your script so errors surface before any git/gh side effects
- Keep a known-good spec around and diff new specs against it
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
- id must match /^a\\d{2,3}$/ (got "${agent.id}")
- branch must match cursor/<id>-<issue>-<slug> (got "${agent.b
- base_repo must be "tinyhumansai/openhuman" (got "${spec.base
- base_branch must be "main" (got "${spec.base_branch}")
- tracking_issue must be a positive integer
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/07aca2c3a268a2e3.
Report an issue: GitHub.