ruvnet/ruflo · error · ClaimOperationError
UNAUTHORIZED
UNAUTHORIZED
Error message
Claimant ${claimant.name} does not own the claim on issue ${issueId} What it means
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.
Source
Thrown at v3/@claude-flow/claims/src/application/claim-service.ts:252
// Emit event
const event = createClaimCreatedEvent(claimId, issueId, claimant);
await this.eventStore.append(event);
return { success: true, claim };
}
async release(issueId: string, claimant: Claimant): Promise<void> {
const claim = await this.claimRepository.findByIssueId(issueId);
// Validate claim exists
if (!claim) {
throw new ClaimOperationError('NOT_CLAIMED', `Issue ${issueId} is not claimed`);
}
// Validate claimant owns the claim
if (claim.claimant.id !== claimant.id) {
throw new ClaimOperationError(
'UNAUTHORIZED',
`Claimant ${claimant.name} does not own the claim on issue ${issueId}`
);
}
// Check for pending handoffs
const pendingHandoff = claim.handoffChain?.find((h) => h.status === 'pending');
if (pendingHandoff) {
throw new ClaimOperationError(
'HANDOFF_PENDING',
`Cannot release claim with pending handoff to ${pendingHandoff.to.name}`
);
}
// Update claim status
const previousStatus = claim.status;
claim.status = 'released';
claim.lastActivityAt = new Date();View on GitHub (pinned to 6b01dc5a68)
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.
Example fix
// before
await service.release(issueId, { id: 'agent-v2', name: 'Agent', type: 'agent' });
// throws UNAUTHORIZED; claim held by 'agent-v1'
// after
const claim = await repo.findByIssueId(issueId);
if (claim) await service.release(issueId, claim.claimant); Defensive patterns
Strategy: validation
Validate before calling
const claim = await claimRepo.findByIssueId(issueId);
if (!claim || claim.claimant.id !== claimant.id) throw new Error('not the holder'); Type guard
null
Try / catch
try { await service.release(issueId, claimant); } catch (e) {
if (e instanceof ClaimOperationError && e.code === 'UNAUTHORIZED') { /* re-fetch holder */ } else throw e;
} Prevention
- Pass the Claimant whose id equals claim.claimant.id.
- Use handoff for delegated transfer instead of release.
When it happens
Trigger: A claimant other than the holder calling release; the holder's identity object has a different id than the one stored on the claim.
Common situations: 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.
Related errors
- Issue ${input.issueId} is not claimed by ${input.claimantId}
- No active claim found for issue ${input.issueId} by ${input.
- NOT_CLAIMED
- HANDOFF_PENDING
- CLAIMANT_NOT_FOUND
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/bbc1fbed5cfd371d.
Report an issue: GitHub.