ruvnet/ruflo · error · Error

Issue ${input.issueId} is already claimed by ${issue.claimed

Error message

Issue ${input.issueId} is already claimed by ${issue.claimedBy}

What it means

Thrown by the simple claimIssue handler right after the not-found check, when the issue exists but issue.claimedBy is already set to another claimant. The message names the current holder so the caller can decide to wait, request a handoff, or steal (if marked stealable).

Source

Thrown at v3/@claude-flow/claims/src/api/mcp-tools.ts:503

    return {
      claimId: claim.id,
      issueId: claim.issueId,
      claimantId: claim.claimantId,
      claimantType: claim.claimantType,
      status: claim.status,
      claimedAt: claim.claimedAt,
      expiresAt: claim.expiresAt,
    };
  }

  // Simple implementation
  const issue = issueStore.get(input.issueId);
  if (!issue) {
    throw new Error(`Issue not found: ${input.issueId}`);
  }

  if (issue.claimedBy) {
    throw new Error(`Issue ${input.issueId} is already claimed by ${issue.claimedBy}`);
  }

  const claimId = generateSecureId('claim');
  const claimedAt = new Date().toISOString();
  const expiresAt = input.expiresInMs
    ? new Date(Date.now() + input.expiresInMs).toISOString()
    : undefined;

  const claim: Claim = {
    id: claimId,
    issueId: input.issueId,
    claimantType: input.claimantType,
    claimantId: input.claimantId,
    status: 'active',
    priority: input.priority || issue.priority,
    stealable: false,
    claimedAt,
    lastActivityAt: claimedAt,

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Check issue.claimedBy before claiming, or call listAvailable to only see unclaimed issues.
  2. If the holder is stale, wait for expiration or use the steal flow (markStealable then steal).
  3. Use the service-backed path (context.claimsService.claim) which emits proper events and handles concurrency via the repository.

Example fix

// before
await claimIssue.handler({ issueId, claimantId: 'bob', claimantType: 'human' });
// throws 67 if issue.claimedBy === 'alice'

// after
const issue = await getIssue.handler({ issueId });
if (!issue.claimedBy) {
  await claimIssue.handler({ issueId, claimantId: 'bob', claimantType: 'human' });
} else {
  // request handoff, or wait, or steal via markStealable + steal
}
Defensive patterns

Strategy: validation

Validate before calling

function isClaimable(issue) { return issue && !issue.claimedBy; }
if (!isClaimable(issueStore.get(input.issueId))) throw new Error('issue not claimable');

Type guard

function isClaimable(issue) { return !!issue && !issue.claimedBy; }

Try / catch

null

Prevention

When it happens

Trigger: Two callers racing to claim the same issue; an issue already assigned in a prior call within the same in-memory process; retrying claim after a timeout without checking state.

Common situations: Concurrent agents grabbing the same ticket; a developer re-running a claim script after it partially succeeded; an issue auto-assigned by a scheduler before manual claim.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/c38ca0c2e4fc71be. Report an issue: GitHub.