{"record":{"id":"a04193780d18ac01","repo":"tinyhumansai/openhuman","slug":"duplicate-issue-agent-issue","errorCode":null,"errorMessage":"duplicate issue #${agent.issue}","messagePattern":"duplicate issue #(.+?)","errorType":"validation","errorClass":"SpecError","httpStatus":null,"severity":"error","filePath":"scripts/agent-batch/lib.mjs","lineNumber":110,"sourceCode":"    }\n    for (const key of REQUIRED_AGENT) {\n      if (!(key in agent))\n        throw new SpecError(`missing required field \"${key}\"`, at);\n    }\n    if (typeof agent.id !== \"string\" || !/^a\\d{2,3}$/.test(agent.id)) {\n      throw new SpecError(\n        `id must match /^a\\\\d{2,3}$/ (got \"${agent.id}\")`,\n        `${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    }","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/a221052e0df5b1f7598fceba7329fd1af95d6699/scripts/agent-batch/lib.mjs#L92-L128","documentation":"Each agent must own a distinct GitHub issue; a second agent reusing an issue number already in the seenIssue Set throws with the number and path \"agents[i].issue\". The design is one-issue-per-agent so PR descriptions, status, and completion can be tracked per agent without ambiguity.","triggerScenarios":"Two agent entries both carrying \"issue\": 5312; a generator assigning the tracking_issue number to every agent; splitting one issue's work across two agents while keeping the same issue field.","commonSituations":"Copying an agent entry and forgetting to update issue; deciding mid-spec that two agents share one issue — the schema forbids it, so the issue must be split upstream on GitHub first.","solutions":["Open a separate GitHub issue for the second agent and update its issue field","If the work truly is one issue, merge the two agents into one entry with combined owned_paths","Verify uniqueness: new Set(agents.map(a => a.issue)).size === agents.length"],"exampleFix":"// before\n{ \"id\": \"a01\", \"issue\": 5312, ... }\n{ \"id\": \"a02\", \"issue\": 5312, ... }\n\n// after\n{ \"id\": \"a01\", \"issue\": 5312, ... }\n{ \"id\": \"a02\", \"issue\": 5313, \"branch\": \"cursor/a02-5313-...\", ... }","handlingStrategy":"validation","validationCode":"const issues = spec.agents.map((a) => a.issue);\nif (new Set(issues).size !== issues.length) {\n  console.error(\"each agent needs its own issue number\");\n  process.exit(1);\n}","typeGuard":null,"tryCatchPattern":"try {\n  validateSpec(spec);\n} catch (e) {\n  if (e instanceof SpecError && /duplicate issue/.test(e.message)) {\n    console.error(`split the work or open a new issue: ${e.message}`);\n    process.exit(1);\n  }\n  throw e;\n}","preventionTips":["Treat one-issue-per-agent as a planning rule, not just a validation rule — split issues on GitHub before speccing","Generate the spec from your issue list so numbers come straight from the source"],"tags":["validation","json","spec","agent-batch","uniqueness","github-issues"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}