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

  1. Check for an existing claim (findByIssueId) before releasing and treat null as already-released/no-op
  2. Catch ClaimOperationError and switch on code === 'NOT_CLAIMED' to skip gracefully in cleanup paths
  3. 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

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


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)