ruvnet/ruflo · error · ClaimOperationError
NOT_CLAIMED
NOT_CLAIMED
Error message
Issue ${issueId} is not claimed What it means
ClaimOperationError with code NOT_CLAIMED, thrown by ClaimService.release in @claude-flow/claims when claimRepository.findByIssueId(issueId) returns null. It means there is no claim record at all for the issue in the backing repository, so releasing is an invalid state transition — distinct from an ownership failure (UNAUTHORIZED) which is checked next.
Solutions
- Check for an existing claim (findByIssueId) before releasing and treat null as already-released/no-op
- Catch ClaimOperationError and switch on code === 'NOT_CLAIMED' to skip gracefully in cleanup paths
- Fix retry logic to re-read claim state instead of blindly repeating release
Example fix
// before await claimService.release(issueId, claimant); // after const existing = await claimRepository.findByIssueId(issueId); if (!existing) return; // nothing to release — idempotent cleanup await claimService.release(issueId, claimant);
Defensive patterns
Strategy: try-catch
Validate before calling
const claim = await claimRepository.findByIssueId(issueId); if (!claim) return; // already unclaimed — release is a no-op await claimService.release(issueId, claimant);
Try / catch
try { await claimService.release(issueId, claimant); } catch (e) { if (e instanceof ClaimOperationError && e.code === 'NOT_CLAIMED') return; /* idempotent */ throw e; } Prevention
- Pre-check findByIssueId in cleanup paths
- Catch ClaimOperationError and branch on .code rather than message text
When it happens
Trigger: Calling release() on an issue whose claim was already released or expired and evicted, or never claimed; repository persistence lost/reset between claim and release; wrong issueId passed to release().
Common situations: Idempotent cleanup routines calling release on every issue in a shutdown sweep, including unclaimed ones; duplicate release calls after a timeout-retry; multi-instance setups where another node already released and the repository state propagated.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- HANDOFF_PENDING
- No active claim found for issue
- No contest to resolve
- UNAUTHORIZED
- Agent is not available (status: )
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/2e0326b1669b2a7b.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/claims/src/application/claim-service.ts:247
reviewers: [],
};
// Save claim
await this.claimRepository.save(claim);
// 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}`
);
}View on GitHub (pinned to fa13ee4ad6)