ruvnet/ruflo · error
Issue is not stealable or not claimed
Error message
Issue ${input.issueId} is not stealable or not claimed What it means
Thrown by the steal handler in @claude-flow/claims when the target issue cannot be stolen: the handler requires an existing claim that is currently claimed AND already flagged stealable (claim.stealable === true). If the claim is missing, released, or the owner never marked it stealable, the steal is rejected with this combined message.
Solutions
- Have the owner mark the claim stealable first (mark-stealable with the owner's claimantId), then steal
- Confirm current state before stealing: claim exists, claimed by someone, stealable === true, and you are not already the owner
- Handle idempotency: if the steal already succeeded, treat 'not stealable or not claimed' as success when you now hold the claim
Example fix
// before
await stealTool({ issueId, claimantId: 'agent-2', claimantType: 'agent' });
// after
const claim = await getClaimTool({ issueId });
if (!claim.stealable && claim.claimantId !== 'agent-2') {
await markStealableTool({ issueId, claimantId: claim.claimantId, reason: 'handoff' });
}
await stealTool({ issueId, claimantId: 'agent-2', claimantType: 'agent' }); Defensive patterns
Strategy: validation
Validate before calling
const claim = await getClaimTool({ issueId });
if (!claim || !claim.claimedBy || claim.claimedBy === claimantId) {
return { stolen: false, reason: 'nothing to steal' };
}
if (!claim.stealable) {
await markStealableTool({ issueId, claimantId: claim.claimantId, reason: 'unblocking' });
}
await stealTool({ issueId, claimantId, claimantType }); Try / catch
try { await stealTool(input); } catch (e) { if (e instanceof Error && e.message.includes('not stealable or not claimed')) { const c = await getClaimTool({ issueId: input.issueId }); if (c.claimantId === input.claimantId) return { stolen: true }; /* already ours */ throw e; } throw e; } Prevention
- Follow the two-step protocol: owner marks stealable, then stealer steals
- Make steal idempotent by re-checking current ownership afterwards
- Verify claim.stealable === true before calling steal
When it happens
Trigger: Calling issue_steal directly on a normally-claimed issue (owner never ran mark-stealable); stealing after the claim was released or after a restart cleared the store; stealing twice — the second call finds the first steal already transferred ownership so the old claim no longer qualifies; using the wrong issueId.
Common situations: Eager workers trying to take over blocked tasks without the courtesy mark-stealable step; retry logic duplicating a steal that already succeeded; races between steal and release.
Related errors
- Issue is already claimed by
- Issue is not claimed by
- Issue not found
- No active claim found for issue
- No contest to resolve
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/5df2a753ec777987.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/claims/src/api/mcp-tools.ts:947
// Update issue
const issue = issueStore.get(input.issueId);
if (issue) {
issue.claimedBy = input.stealerId;
}
return {
stolen: true,
issueId: input.issueId,
newClaimId,
previousClaimant,
contestWindowMs: 300000, // 5 minutes
stolenAt,
};
}
}
throw new Error(`Issue ${input.issueId} is not stealable or not claimed`);
}
/**
* Get stealable issues
*/
async function handleIssueGetStealable(
input: z.infer<typeof issueGetStealableSchema>,
context?: ToolContext
): Promise<{
issues: Array<{
issueId: string;
title: string;
priority: IssuePriority;
currentClaimant: string;
stealableReason?: string;
}>;
total: number;
}> {View on GitHub (pinned to fa13ee4ad6)