{"record":{"id":"97581530529bee8f","repo":"ruvnet/ruflo","slug":"handoff-not-found","errorCode":"HANDOFF_NOT_FOUND","errorMessage":"No pending handoff found for claimant ${claimant.name}","messagePattern":"No pending handoff found for claimant (.+?)","errorType":"error_code","errorClass":"ClaimOperationError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/claims/src/application/claim-service.ts","lineNumber":370,"sourceCode":"    );\n    await this.eventStore.append(statusEvent);\n  }\n\n  async acceptHandoff(issueId: string, claimant: Claimant): Promise<void> {\n    const claim = await this.claimRepository.findByIssueId(issueId);\n\n    // Validate claim exists\n    if (!claim) {\n      throw new ClaimOperationError('NOT_CLAIMED', `Issue ${issueId} is not claimed`);\n    }\n\n    // Find pending handoff for this claimant\n    const pendingHandoff = claim.handoffChain?.find(\n      (h) => h.status === 'pending' && h.to.id === claimant.id\n    );\n\n    if (!pendingHandoff) {\n      throw new ClaimOperationError(\n        'HANDOFF_NOT_FOUND',\n        `No pending handoff found for claimant ${claimant.name}`\n      );\n    }\n\n    // Check claimant's workload\n    const currentClaimCount = await this.claimRepository.countByClaimant(claimant.id);\n    const maxClaims = claimant.maxConcurrentClaims ?? 5;\n    if (currentClaimCount >= maxClaims) {\n      throw new ClaimOperationError(\n        'MAX_CLAIMS_EXCEEDED',\n        `Cannot accept handoff: claimant ${claimant.name} at max capacity`\n      );\n    }\n\n    // Update handoff record\n    pendingHandoff.status = 'accepted';\n    pendingHandoff.resolvedAt = new Date();","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/claims/src/application/claim-service.ts#L352-L388","documentation":"Thrown by acceptHandoff when a claim exists but claim.handoffChain contains no entry with status 'pending' AND h.to.id === claimant.id. The handoff was never requested, targets a different claimant, or has already been accepted/rejected and is no longer pending.","triggerScenarios":"Passing a claimant whose id differs from the handoff's to.id; calling acceptHandoff after the pending entry was already resolved (accepted/rejected); the handoff was cancelled; only a release (not a handoff) was performed on the claim.","commonSituations":"Agent id format mismatch between the agent registry and the Claimant object used at request time; concurrent accept by two workers that both consumed the same notification; stale notification replayed after the first accept resolved the handoff.","solutions":["Pass the exact Claimant object (matching id) that was the 'to' target of the original requestHandoff call.","Inspect claim.handoffChain via getIssueStatus and confirm a pending entry exists for claimant.id before accepting.","If the handoff was already accepted, treat the operation as a no-op success rather than re-accepting.","If the prior handoff was rejected, request a new handoff via requestHandoff instead of accepting."],"exampleFix":"// before\nawait claimService.acceptHandoff(issueId, { id: agentRef.code, ... }); // wrong id scheme\n\n// after\nconst target = handoffRecord.to; // the claimant recorded at request time\nawait claimService.acceptHandoff(issueId, target);","handlingStrategy":"validation","validationCode":"const status = await claimService.getIssueStatus(issueId);\nconst pending = status.claim?.handoffChain?.find(\n  h => h.status === 'pending' && h.to.id === claimant.id\n);\nif (!pending) {\n  throw new Error(`No pending handoff targets ${claimant.id}`);\n}\nawait claimService.acceptHandoff(issueId, claimant);","typeGuard":"function hasPendingHandoffFor(claim: IssueClaim | null, claimantId: string): claim is IssueClaim {\n  return !!claim && !!claim.handoffChain?.some(h => h.status === 'pending' && h.to.id === claimantId);\n}","tryCatchPattern":"try {\n  await claimService.acceptHandoff(issueId, claimant);\n} catch (e) {\n  if (e instanceof ClaimOperationError && e.code === 'HANDOFF_NOT_FOUND') {\n    // already resolved or targets someone else; log and move on\n    return;\n  }\n  throw e;\n}","preventionTips":["Carry the exact target Claimant object with the handoff notification instead of reconstructing it.","Key claimants by a stable, canonical id and normalize at all boundaries.","Make accept/reject idempotent on the caller side by tracking resolved handoff ids."],"tags":["claims","handoff","id-mismatch","precondition"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}