ruvnet/ruflo · error · ClaimOperationError

HANDOFF_PENDING

HANDOFF_PENDING

Error message

Cannot release claim with pending handoff to ${pendingHandoff.to.name}

What it means

ClaimOperationError with code HANDOFF_PENDING, thrown by ClaimService.release when the claim's handoffChain contains a record with status 'pending'. A claim that is mid-handoff to another claimant cannot be released until that handoff is completed or cancelled, preventing the target claimant from receiving a dead claim.

Solutions

  1. Complete the pending handoff first: have the target claimant accept (or reject) it, then release or proceed
  2. Cancel the pending handoff via the service's handoff cancellation path if the target is unreachable, then release
  3. Order shutdown flows as: resolve handoffs -> release claims, and skip release for claims with pending handoffs

Example fix

// before
await claimService.requestHandoff(issueId, from, to, 'shift end');
await claimService.release(issueId, from); // throws HANDOFF_PENDING

// after
await claimService.requestHandoff(issueId, from, to, 'shift end');
await claimService.acceptHandoff(issueId, to); // or rejectHandoff to cancel
await claimService.release(issueId, from); // handoff no longer pending
Defensive patterns

Strategy: try-catch

Validate before calling

const claim = await claimRepository.findByIssueId(issueId);
const pending = claim?.handoffChain?.find(h => h.status === 'pending');
if (pending) {
  await claimService.cancelHandoff(issueId, pending); // or wait for accept/reject
}
await claimService.release(issueId, claimant);

Try / catch

try { await claimService.release(issueId, claimant); } catch (e) { if (e instanceof ClaimOperationError && e.code === 'HANDOFF_PENDING') { await resolveOrCancelPendingHandoff(issueId); return claimService.release(issueId, claimant); } throw e; }

Prevention

When it happens

Trigger: Calling release() after requestHandoff() but before the target accepts/rejects the handoff; orchestrators running release-all sweeps that hit claims in transfer; crashed handoff flows leaving a permanently pending record that then blocks release.

Common situations: Agent handoff at end of shift followed by immediate cleanup release; handoff target never responding so the pending record wedges the claim; automated teardown during a redeploy catching in-flight handoffs.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/10229aad006d3add. Report an issue: GitHub.

Appendix: 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 fa13ee4ad6)