ruvnet/ruflo · error · ClaimOperationError
HANDOFF_PENDING
HANDOFF_PENDING
Error message
Cannot release claim with pending handoff to ${pendingHandoff.to.name} What it means
Thrown by ClaimService.release when claim.handoffChain contains a handoff with status 'pending'. A claim mid-handoff cannot be released — doing so would orphan the pending transfer. Code is HANDOFF_PENDING, and the message names the pending handoff's target.
Source
Thrown at v3/@claude-flow/claims/src/application/claim-service.ts:261
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();
await this.claimRepository.save(claim);
// Emit events
const releaseEvent = createClaimReleasedEvent(claim.id, issueId, claimant);
await this.eventStore.append(releaseEvent);
if (previousStatus !== 'released') {
const statusEvent = createClaimStatusChangedEvent(View on GitHub (pinned to 6b01dc5a68)
Solutions
- Resolve the pending handoff first (accept or reject via the handoff APIs) before releasing.
- If the handoff is stale, add a handoff cancellation/expire step (or extend the service) then release.
- Check claim.handoffChain for any pending entry before calling release.
Example fix
// before
await service.release(issueId, claimant); // throws HANDOFF_PENDING
// after
const pending = claim.handoffChain?.find(h => h.status === 'pending');
if (pending) {
await service.respondHandoff(issueId, pending.id, 'rejected', /*by*/ pending.to);
}
await service.release(issueId, claimant); Defensive patterns
Strategy: validation
Validate before calling
function hasPendingHandoff(claim) {
return !!claim?.handoffChain?.some(h => h.status === 'pending');
} Type guard
function hasPendingHandoff(claim) { return !!claim?.handoffChain?.some(h => h.status === 'pending'); } Try / catch
try { await service.release(issueId, claimant); } catch (e) {
if (e instanceof ClaimOperationError && e.code === 'HANDOFF_PENDING') { /* resolve handoff first */ } else throw e;
} Prevention
- Resolve pending handoffs (accept/reject) before release.
- Add a handoff expire/cancel path for abandoned handoffs.
When it happens
Trigger: Calling release after requestHandoff but before the target accepts/rejects; a handoff that was requested and never resolved.
Common situations: A handoff abandoned by the target; the caller forgetting a handoff is in flight; orchestrator releasing a worker's claim while a handoff to another worker is pending.
Related errors
- CLAIMANT_NOT_FOUND
- Issue ${input.issueId} is not stealable or not claimed
- NOT_CLAIMED
- UNAUTHORIZED
- Invalid target format: ${target}. Use agent:<id> or human:<i
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/10229aad006d3add.
Report an issue: GitHub.