{"record":{"id":"59731312a2d38892","repo":"tinyhumansai/openhuman","slug":"branch-must-match-cursor-id-issue-slug-got","errorCode":null,"errorMessage":"branch must match cursor/<id>-<issue>-<slug> (got \"${agent.branch}\")","messagePattern":"branch must match cursor/<id>-<issue>-<slug> \\(got \"(.+?)\"\\)","errorType":"validation","errorClass":"SpecError","httpStatus":null,"severity":"error","filePath":"scripts/agent-batch/lib.mjs","lineNumber":118,"sourceCode":"        `${at}.id`,\n      );\n    }\n    if (seenId.has(agent.id))\n      throw new SpecError(`duplicate id \"${agent.id}\"`, `${at}.id`);\n    seenId.add(agent.id);\n    if (!Number.isInteger(agent.issue) || agent.issue <= 0) {\n      throw new SpecError(\"issue must be a positive integer\", `${at}.issue`);\n    }\n    if (seenIssue.has(agent.issue)) {\n      throw new SpecError(`duplicate issue #${agent.issue}`, `${at}.issue`);\n    }\n    seenIssue.add(agent.issue);\n    if (typeof agent.title !== \"string\" || agent.title.trim().length === 0) {\n      throw new SpecError(\"title must be a non-empty string\", `${at}.title`);\n    }\n    const m = BRANCH_RE.exec(agent.branch);\n    if (!m) {\n      throw new SpecError(\n        `branch must match cursor/<id>-<issue>-<slug> (got \"${agent.branch}\")`,\n        `${at}.branch`,\n      );\n    }\n    if (m[1] !== agent.id) {\n      throw new SpecError(\n        `branch id segment \"${m[1]}\" does not match agent id \"${agent.id}\"`,\n        `${at}.branch`,\n      );\n    }\n    if (Number(m[2]) !== agent.issue) {\n      throw new SpecError(\n        `branch issue segment \"${m[2]}\" does not match agent issue ${agent.issue}`,\n        `${at}.branch`,\n      );\n    }\n    if (seenBranch.has(agent.branch)) {\n      throw new SpecError(`duplicate branch \"${agent.branch}\"`, `${at}.branch`);","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/agent-batch/lib.mjs#L100-L136","documentation":"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\".","triggerScenarios":"\"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).","commonSituations":"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.","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)"],"exampleFix":"// before\n\"branch\": \"feature/fix_ci_timeout\"\n\n// after\n\"branch\": \"cursor/a01-5312-fix-ci-timeout\"","handlingStrategy":"validation","validationCode":"import { BRANCH_RE } from \"./scripts/agent-batch/lib.mjs\";\nfor (const [i, a] of spec.agents.entries()) {\n  if (!BRANCH_RE.test(a.branch)) {\n    console.error(`agents[${i}].branch must match cursor/<id>-<issue>-<slug> (got ${a.branch})`);\n    process.exit(1);\n  }\n}","typeGuard":"/** @param {unknown} v */\nfunction isBatchBranch(v) {\n  return typeof v === \"string\" && /^cursor\\/a\\d{2,3}-\\d+-[a-z0-9][a-z0-9-]*$/.test(v);\n}","tryCatchPattern":"try {\n  validateSpec(spec);\n} catch (e) {\n  if (e instanceof SpecError && e.path?.endsWith(\".branch\")) {\n    console.error(`branch format/policy bad: ${e.message}`);\n    process.exit(1);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["validation","json","spec","agent-batch","git","naming"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}