{"record":{"id":"c38ca0c2e4fc71be","repo":"ruvnet/ruflo","slug":"issue-input-issueid-is-already-claimed-by-iss","errorCode":null,"errorMessage":"Issue ${input.issueId} is already claimed by ${issue.claimedBy}","messagePattern":"Issue (.+?) is already claimed by (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/claims/src/api/mcp-tools.ts","lineNumber":503,"sourceCode":"    return {\n      claimId: claim.id,\n      issueId: claim.issueId,\n      claimantId: claim.claimantId,\n      claimantType: claim.claimantType,\n      status: claim.status,\n      claimedAt: claim.claimedAt,\n      expiresAt: claim.expiresAt,\n    };\n  }\n\n  // Simple implementation\n  const issue = issueStore.get(input.issueId);\n  if (!issue) {\n    throw new Error(`Issue not found: ${input.issueId}`);\n  }\n\n  if (issue.claimedBy) {\n    throw new Error(`Issue ${input.issueId} is already claimed by ${issue.claimedBy}`);\n  }\n\n  const claimId = generateSecureId('claim');\n  const claimedAt = new Date().toISOString();\n  const expiresAt = input.expiresInMs\n    ? new Date(Date.now() + input.expiresInMs).toISOString()\n    : undefined;\n\n  const claim: Claim = {\n    id: claimId,\n    issueId: input.issueId,\n    claimantType: input.claimantType,\n    claimantId: input.claimantId,\n    status: 'active',\n    priority: input.priority || issue.priority,\n    stealable: false,\n    claimedAt,\n    lastActivityAt: claimedAt,","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/claims/src/api/mcp-tools.ts#L485-L521","documentation":"Thrown by the claim-issue MCP tool handler in @claude-flow/claims when the issue exists but issue.claimedBy is already set, i.e. another claimant holds an active claim. This is the double-claim guard in the 'Simple implementation' path and fires before a new Claim record is created.","triggerScenarios":"Two agents calling the claim tool for the same issueId concurrently or sequentially while the first claim is still active; retrying a claim after a network timeout when the first attempt actually succeeded; claiming an issue whose prior owner never called release.","commonSituations":"Multiple swarm workers scanning the same backlog and racing for tasks; at-least-once retry logic in the client duplicating a successful claim; stale claims from crashed agents that never released, permanently blocking the issue.","solutions":["Have the current holder call the release tool (or release with the owner's claimantId) before re-claiming","If the holder is defunct, use the mark-stealable + steal flow (issue_mark_stealable then issue_steal) to take over the claim","Query the issue state first and skip claiming when claimedBy is set, treating the issue as taken","Make claim attempts idempotent per agent: if already claimed by the same claimantId, proceed instead of erroring"],"exampleFix":"// before\nawait claimTool({ issueId, claimantId: 'agent-2', claimantType: 'agent' }); // agent-1 holds it\n\n// after\nconst issue = await getIssueTool({ issueId });\nif (issue.claimedBy && issue.claimedBy !== 'agent-2') {\n  await markStealableTool({ issueId, claimantId: issue.claimedBy, reason: 'owner unresponsive' });\n  await stealTool({ issueId, claimantId: 'agent-2', claimantType: 'agent' });\n} else {\n  await claimTool({ issueId, claimantId: 'agent-2', claimantType: 'agent' });\n}","handlingStrategy":"validation","validationCode":"const issue = issueStoreSnapshot.get(issueId); // via get-issue tool\nif (issue?.claimedBy && issue.claimedBy !== claimantId) {\n  return { skipped: true, reason: `taken by ${issue.claimedBy}` };\n}\nawait claimTool({ issueId, claimantId, claimantType });","typeGuard":null,"tryCatchPattern":"try { await claimTool(input); } catch (e) { if (e instanceof Error && e.message.includes('is already claimed by')) { const holder = e.message.split('claimed by ')[1]; await coordinateWith(holder); } throw e; }","preventionTips":["Make claim attempts idempotent per agent (skip when already yours)","Use stealable/steal for stuck claims instead of re-claiming","Design workers to claim via a single coordinator to avoid races"],"tags":["claims","mcp-tools","double-claim","concurrency"],"backgroundTag":"resource-already-claimed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}