{"record":{"id":"8e6f85579abd748b","repo":"ruvnet/ruflo","slug":"duplicate-agent-id-agent-id","errorCode":null,"errorMessage":"duplicate agent id: ${agent.id}","messagePattern":"duplicate agent id: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/worktrees/coordinator.ts","lineNumber":57,"sourceCode":"  constructor(repoRoot: string) {\n    this.repoRoot = resolve(repoRoot);\n    const top = git(this.repoRoot, ['rev-parse', '--show-toplevel']);\n    if (resolve(top) !== this.repoRoot) throw new Error(`repoRoot must be the git top-level: ${top}`);\n    this.registryDir = join(this.repoRoot, '.claude-flow', 'swarm', 'worktrees');\n    this.worktreeBase = join(dirname(this.repoRoot), '.ruflo-worktrees', basename(this.repoRoot));\n  }\n\n  prepare(\n    runId: string,\n    agents: Array<{ id: string; readOnly?: boolean }>,\n    options: { allowDirty?: boolean; baseRef?: string } = {},\n  ): WorktreeRunRecord {\n    assertId(runId, 'run id');\n    if (!agents.length) throw new Error('at least one agent is required');\n    const unique = new Set<string>();\n    for (const agent of agents) {\n      assertId(agent.id, 'agent id');\n      if (unique.has(agent.id)) throw new Error(`duplicate agent id: ${agent.id}`);\n      unique.add(agent.id);\n    }\n    const registryPath = this.registryPath(runId);\n    if (existsSync(registryPath)) return this.status(runId);\n    if (!options.allowDirty && git(this.repoRoot, ['status', '--porcelain']).length > 0) {\n      throw new Error('refusing to prepare writing worktrees from a dirty repository');\n    }\n\n    mkdirSync(this.registryDir, { recursive: true });\n    mkdirSync(join(this.worktreeBase, runId), { recursive: true });\n    const assignments: WorktreeAssignment[] = [];\n    try {\n      for (const agent of agents) {\n        const readOnly = agent.readOnly === true;\n        const branch = `ruflo/${runId}/${agent.id}`;\n        const worktreePath = join(this.worktreeBase, runId, agent.id);\n        if (readOnly) {\n          git(this.repoRoot, ['worktree', 'add', '--detach', worktreePath, options.baseRef ?? 'HEAD']);","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/worktrees/coordinator.ts#L39-L75","documentation":"CodexWorktreeCoordinator.prepare() gives every agent in a run its own git worktree and branch named ruflo/<runId>/<agent.id>, so agent ids must be unique within one prepare() call. Ids are collected into a Set and the call throws as soon as a repeat is seen, before any worktree or registry file is created. A duplicate would otherwise collide branch names and registry assignments for the run.","triggerScenarios":"prepare('run-1', [{ id: 'coder-1' }, { id: 'coder-1' }]) — any agents array containing two entries with the same id string. Ids must also match SAFE_ID /^[a-z0-9][a-z0-9._-]{0,63}$/ (enforced separately by assertId).","commonSituations":"Generating agent rosters in a loop that resets its counter, spawning two role-named agents ('coder') without an index suffix, or copy-pasting an agent list entry when adding a reviewer.","solutions":["Deduplicate or rename agents before calling prepare(): suffix ids with an index (coder-1, coder-2) derived from the loop counter","If two workers were meant to share one checkout, keep a single entry (extra readers can be marked readOnly) instead of duplicating the id","Assert uniqueness early with new Set(agents.map(a => a.id)).size === agents.length so the failure points at your caller code, not deep inside the library"],"exampleFix":"// before\nconst agents = roles.flatMap(r => [{ id: r.name }, { id: r.name }]);\ncoordinator.prepare('run-1', agents); // throws: duplicate agent id\n\n// after\nconst agents = roles.map((r, i) => ({ id: `${r.name}-${i + 1}` }));\ncoordinator.prepare('run-1', agents);","handlingStrategy":"validation","validationCode":"const SAFE_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;\nfunction assertPreparable(runId: string, agents: Array<{ id: string }>): void {\n  const ids = agents.map(a => a.id);\n  if (new Set(ids).size !== ids.length) {\n    const dup = ids.find((id, i) => ids.indexOf(id) !== i);\n    throw new Error(`agents contain a duplicate id: ${dup}`);\n  }\n  for (const id of ids) if (!SAFE_ID.test(id)) throw new Error(`agent id not SAFE_ID: ${id}`);\n}\nassertPreparable(runId, agents);\ncoordinator.prepare(runId, agents);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive agent ids from a guaranteed-unique source such as run id + loop index","Generate ids from the SAFE_ID charset ([a-z0-9][a-z0-9._-]{0,63}) so they also pass assertId"],"tags":["worktrees","git","duplicate-id","validation","multi-agent"],"backgroundTag":"duplicate-entity-id","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}