tinyhumansai/openhuman · error · SpecError
branch must match cursor/<id>-<issue>-<slug> (got "${agent.b
Error message
branch must match cursor/<id>-<issue>-<slug> (got "${agent.branch}") What it means
agent.branch must fully match BRANCH_RE = /^cursor\/(a\d{2,3})-(\d+)-[a-z0-9][a-z0-9-]*$/, i.e. cursor/<agentId>-<issueNumber>-<slug> where the slug is lowercase kebab starting with a letter/digit. The regex is exec'd (not test) because capture groups 1 and 2 are cross-checked against agent.id and agent.issue afterwards. The whole offending branch string is shown, with path "agents[i].branch".
Source
Thrown at scripts/agent-batch/lib.mjs:118
`${at}.id`,
);
}
if (seenId.has(agent.id))
throw new SpecError(`duplicate id "${agent.id}"`, `${at}.id`);
seenId.add(agent.id);
if (!Number.isInteger(agent.issue) || agent.issue <= 0) {
throw new SpecError("issue must be a positive integer", `${at}.issue`);
}
if (seenIssue.has(agent.issue)) {
throw new SpecError(`duplicate issue #${agent.issue}`, `${at}.issue`);
}
seenIssue.add(agent.issue);
if (typeof agent.title !== "string" || agent.title.trim().length === 0) {
throw new SpecError("title must be a non-empty string", `${at}.title`);
}
const m = BRANCH_RE.exec(agent.branch);
if (!m) {
throw new SpecError(
`branch must match cursor/<id>-<issue>-<slug> (got "${agent.branch}")`,
`${at}.branch`,
);
}
if (m[1] !== agent.id) {
throw new SpecError(
`branch id segment "${m[1]}" does not match agent id "${agent.id}"`,
`${at}.branch`,
);
}
if (Number(m[2]) !== agent.issue) {
throw new SpecError(
`branch issue segment "${m[2]}" does not match agent issue ${agent.issue}`,
`${at}.branch`,
);
}
if (seenBranch.has(agent.branch)) {
throw new SpecError(`duplicate branch "${agent.branch}"`, `${at}.branch`);View on GitHub (pinned to a221052e0d)
Solutions
- Rewrite the branch as cursor/<id>-<issue>-<kebab-slug>, e.g. cursor/a01-5312-fix-ci-timeout
- Lowercase the entire branch and replace underscores/spaces with dashes
- Make sure the id and issue segments match the agent's own id/issue fields (errors 194/195 follow otherwise)
Example fix
// before "branch": "feature/fix_ci_timeout" // after "branch": "cursor/a01-5312-fix-ci-timeout"
Defensive patterns
Strategy: validation
Validate before calling
import { BRANCH_RE } from "./scripts/agent-batch/lib.mjs";
for (const [i, a] of spec.agents.entries()) {
if (!BRANCH_RE.test(a.branch)) {
console.error(`agents[${i}].branch must match cursor/<id>-<issue>-<slug> (got ${a.branch})`);
process.exit(1);
}
} Type guard
/** @param {unknown} v */
function isBatchBranch(v) {
return typeof v === "string" && /^cursor\/a\d{2,3}-\d+-[a-z0-9][a-z0-9-]*$/.test(v);
} Try / catch
try {
validateSpec(spec);
} catch (e) {
if (e instanceof SpecError && e.path?.endsWith(".branch")) {
console.error(`branch format/policy bad: ${e.message}`);
process.exit(1);
}
throw e;
} Prevention
- Never hand-write branches: derive them as `cursor/${a.id}-${a.issue}-${slugify(a.title)}`
- slugify = lowercase, [^a-z0-9]+ → dash, trim dashes — keeps the slug segment valid by construction
- Import BRANCH_RE from lib.mjs and reuse it in generators instead of re-typing the regex
When it happens
Trigger: "branch": "feature/a01-5312-fix" (wrong prefix), "cursor/a01_5312_fix" (underscores), "cursor/A01-5312-fix" (uppercase id segment), "cursor/a01-5312-Fix_CI" (uppercase slug), missing slug "cursor/a01-5312-", or a branch field that is not a string (exec on non-string coerces and fails to match).
Common situations: Reusing ordinary feature-branch names instead of the batch convention; uppercase in the slug copied from a title; underscores from snake_case habits; forgetting the trailing slug entirely.
Related errors
- batch_id must be a kebab-case slug
- base_branch must be "main" (got "${spec.base_branch}")
- id must match /^a\\d{2,3}$/ (got "${agent.id}")
- duplicate branch "${agent.branch}"
- base_repo must be "tinyhumansai/openhuman" (got "${spec.base
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/59731312a2d38892.
Report an issue: GitHub.