{"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":"Error","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/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/claims/src/api/mcp-tools.ts#L485-L521","documentation":"Thrown by the simple claimIssue handler right after the not-found check, when the issue exists but issue.claimedBy is already set to another claimant. The message names the current holder so the caller can decide to wait, request a handoff, or steal (if marked stealable).","triggerScenarios":"Two callers racing to claim the same issue; an issue already assigned in a prior call within the same in-memory process; retrying claim after a timeout without checking state.","commonSituations":"Concurrent agents grabbing the same ticket; a developer re-running a claim script after it partially succeeded; an issue auto-assigned by a scheduler before manual claim.","solutions":["Check issue.claimedBy before claiming, or call listAvailable to only see unclaimed issues.","If the holder is stale, wait for expiration or use the steal flow (markStealable then steal).","Use the service-backed path (context.claimsService.claim) which emits proper events and handles concurrency via the repository."],"exampleFix":"// before\nawait claimIssue.handler({ issueId, claimantId: 'bob', claimantType: 'human' });\n// throws 67 if issue.claimedBy === 'alice'\n\n// after\nconst issue = await getIssue.handler({ issueId });\nif (!issue.claimedBy) {\n  await claimIssue.handler({ issueId, claimantId: 'bob', claimantType: 'human' });\n} else {\n  // request handoff, or wait, or steal via markStealable + steal\n}","handlingStrategy":"validation","validationCode":"function isClaimable(issue) { return issue && !issue.claimedBy; }\nif (!isClaimable(issueStore.get(input.issueId))) throw new Error('issue not claimable');","typeGuard":"function isClaimable(issue) { return !!issue && !issue.claimedBy; }","tryCatchPattern":"null","preventionTips":["Use listAvailable to enumerate only unclaimed issues.","For contested issues, implement claim retries with backoff or use the steal flow."],"tags":["claims","mcp","concurrency","already-claimed","typescript"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}