ruvnet/ruflo · error · Error

No pending handoff for issue ${issueId}

Error message

No pending handoff for issue ${issueId}

What it means

acceptHandoff(issueId, claimant) requires a claim in status 'handoff-pending'. If no claim exists or its status is anything else (active, blocked, completed, etc.), it throws 'No pending handoff for issue ...'. The handoff is not accepted and no claimant change occurs.

Source

Thrown at v3/@claude-flow/cli/src/services/claim-service.ts:358

    claim.status = 'handoff-pending';
    claim.statusChangedAt = new Date();
    claim.handoffTo = to;
    claim.handoffReason = reason;
    await this.saveClaims();

    this.emitEvent({
      type: 'issue:handoff:requested',
      timestamp: new Date(),
      issueId,
      claimant: from,
      data: { to, reason },
    });
  }

  async acceptHandoff(issueId: string, claimant: Claimant): Promise<void> {
    const claim = this.claims.get(issueId);
    if (!claim || claim.status !== 'handoff-pending') {
      throw new Error(`No pending handoff for issue ${issueId}`);
    }

    if (!claim.handoffTo || !this.isSameClaimant(claim.handoffTo, claimant)) {
      throw new Error(`Handoff not addressed to ${this.formatClaimant(claimant)}`);
    }

    const previousClaimant = claim.claimant;
    claim.claimant = claimant;
    claim.status = 'active';
    claim.statusChangedAt = new Date();
    delete claim.handoffTo;
    delete claim.handoffReason;
    await this.saveClaims();

    this.emitEvent({
      type: 'issue:handoff:accepted',
      timestamp: new Date(),
      issueId,

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Only call acceptHandoff in response to an issue:handoff:requested event you actually received.
  2. Make accept idempotent: catch 'No pending handoff' and verify the claim is already in the desired state before treating as error.
  3. Use a correlation id so duplicate accepts are deduped upstream.

Example fix

// before: blind retry
await claims.acceptHandoff(issueId, me); // throws on second attempt

// after: idempotent accept
try {
  await claims.acceptHandoff(issueId, me);
} catch (e) {
  if (!String(e.message).includes('No pending handoff')) throw e;
  // verify we already hold it; if so, success
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await claims.acceptHandoff(issueId, claimant);
} catch (e) {
  if (String(e.message).includes('No pending handoff')) {
    // verify we already hold it; if so, treat as success
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling acceptHandoff before requestHandoff was called; calling it after the handoff was already accepted/rejected/cancelled; calling it on an issue whose handoff timed out and reverted to active; double-accept.

Common situations: Network retries that re-deliver an accept after the first succeeded; race between accept and reject; UI accepting a handoff that was concurrently cancelled.

Related errors


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