{"record":{"id":"bbc1fbed5cfd371d","repo":"ruvnet/ruflo","slug":"unauthorized-bbc1fb","errorCode":"UNAUTHORIZED","errorMessage":"Claimant ${claimant.name} does not own the claim on issue ${issueId}","messagePattern":"Claimant (.+?) does not own the claim on issue (.+?)","errorType":"error_code","errorClass":"ClaimOperationError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/claims/src/application/claim-service.ts","lineNumber":252,"sourceCode":"\n    // Emit event\n    const event = createClaimCreatedEvent(claimId, issueId, claimant);\n    await this.eventStore.append(event);\n\n    return { success: true, claim };\n  }\n\n  async release(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    // Validate claimant owns the claim\n    if (claim.claimant.id !== claimant.id) {\n      throw new ClaimOperationError(\n        'UNAUTHORIZED',\n        `Claimant ${claimant.name} does not own the claim on issue ${issueId}`\n      );\n    }\n\n    // Check for pending handoffs\n    const pendingHandoff = claim.handoffChain?.find((h) => h.status === 'pending');\n    if (pendingHandoff) {\n      throw new ClaimOperationError(\n        'HANDOFF_PENDING',\n        `Cannot release claim with pending handoff to ${pendingHandoff.to.name}`\n      );\n    }\n\n    // Update claim status\n    const previousStatus = claim.status;\n    claim.status = 'released';\n    claim.lastActivityAt = new Date();","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/claims/src/application/claim-service.ts#L234-L270","documentation":"Thrown by ClaimService.release when a claim exists but claim.claimant.id !== claimant.id. The release operation is owner-only at the service layer; this guards against a non-holder releasing someone else's claim. Code is UNAUTHORIZED.","triggerScenarios":"A claimant other than the holder calling release; the holder's identity object has a different id than the one stored on the claim.","commonSituations":"Agent restart produced a new id; a supervisor trying to release on behalf of a worker without using handoff; passing a Claimant with a typo'd id.","solutions":["Pass the Claimant whose id equals claim.claimant.id (look it up first).","For delegated release, extend the service with an authorized override rather than bypassing the check.","Use requestHandoff to transfer ownership to the new claimant."],"exampleFix":"// before\nawait service.release(issueId, { id: 'agent-v2', name: 'Agent', type: 'agent' });\n// throws UNAUTHORIZED; claim held by 'agent-v1'\n\n// after\nconst claim = await repo.findByIssueId(issueId);\nif (claim) await service.release(issueId, claim.claimant);","handlingStrategy":"validation","validationCode":"const claim = await claimRepo.findByIssueId(issueId);\nif (!claim || claim.claimant.id !== claimant.id) throw new Error('not the holder');","typeGuard":"null","tryCatchPattern":"try { await service.release(issueId, claimant); } catch (e) {\n  if (e instanceof ClaimOperationError && e.code === 'UNAUTHORIZED') { /* re-fetch holder */ } else throw e;\n}","preventionTips":["Pass the Claimant whose id equals claim.claimant.id.","Use handoff for delegated transfer instead of release."],"tags":["claims","ddd","authorization","ownership","typescript"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}