{"record":{"id":"407e5f63e94b4269","repo":"ruvnet/ruflo","slug":"no-pending-handoff-for-issue-issueid","errorCode":null,"errorMessage":"No pending handoff for issue ${issueId}","messagePattern":"No pending handoff for issue (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/services/claim-service.ts","lineNumber":358,"sourceCode":"    claim.status = 'handoff-pending';\n    claim.statusChangedAt = new Date();\n    claim.handoffTo = to;\n    claim.handoffReason = reason;\n    await this.saveClaims();\n\n    this.emitEvent({\n      type: 'issue:handoff:requested',\n      timestamp: new Date(),\n      issueId,\n      claimant: from,\n      data: { to, reason },\n    });\n  }\n\n  async acceptHandoff(issueId: string, claimant: Claimant): Promise<void> {\n    const claim = this.claims.get(issueId);\n    if (!claim || claim.status !== 'handoff-pending') {\n      throw new Error(`No pending handoff for issue ${issueId}`);\n    }\n\n    if (!claim.handoffTo || !this.isSameClaimant(claim.handoffTo, claimant)) {\n      throw new Error(`Handoff not addressed to ${this.formatClaimant(claimant)}`);\n    }\n\n    const previousClaimant = claim.claimant;\n    claim.claimant = claimant;\n    claim.status = 'active';\n    claim.statusChangedAt = new Date();\n    delete claim.handoffTo;\n    delete claim.handoffReason;\n    await this.saveClaims();\n\n    this.emitEvent({\n      type: 'issue:handoff:accepted',\n      timestamp: new Date(),\n      issueId,","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/services/claim-service.ts#L340-L376","documentation":"acceptHandoff(issueId, claimant) requires a claim in status 'handoff-pending'. If no claim exists or its status is anything else (active, blocked, completed, etc.), it throws 'No pending handoff for issue ...'. The handoff is not accepted and no claimant change occurs.","triggerScenarios":"Calling acceptHandoff before requestHandoff was called; calling it after the handoff was already accepted/rejected/cancelled; calling it on an issue whose handoff timed out and reverted to active; double-accept.","commonSituations":"Network retries that re-deliver an accept after the first succeeded; race between accept and reject; UI accepting a handoff that was concurrently cancelled.","solutions":["Only call acceptHandoff in response to an issue:handoff:requested event you actually received.","Make accept idempotent: catch 'No pending handoff' and verify the claim is already in the desired state before treating as error.","Use a correlation id so duplicate accepts are deduped upstream."],"exampleFix":"// before: blind retry\nawait claims.acceptHandoff(issueId, me); // throws on second attempt\n\n// after: idempotent accept\ntry {\n  await claims.acceptHandoff(issueId, me);\n} catch (e) {\n  if (!String(e.message).includes('No pending handoff')) throw e;\n  // verify we already hold it; if so, success\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await claims.acceptHandoff(issueId, claimant);\n} catch (e) {\n  if (String(e.message).includes('No pending handoff')) {\n    // verify we already hold it; if so, treat as success\n    return;\n  }\n  throw e;\n}","preventionTips":["Only accept in response to a real issue:handoff:requested event.","Make accept idempotent in retry paths by catching 'No pending handoff'.","Use correlation ids so duplicate accepts are deduped upstream."],"tags":["claim-service","handoff","lifecycle","state-machine"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}