ruvnet/ruflo · error · ClaimOperationError
HANDOFF_NOT_FOUND
HANDOFF_NOT_FOUND
Error message
No pending handoff found for claimant ${claimant.name} What it means
Thrown by acceptHandoff when a claim exists but claim.handoffChain contains no entry with status 'pending' AND h.to.id === claimant.id. The handoff was never requested, targets a different claimant, or has already been accepted/rejected and is no longer pending.
Source
Thrown at v3/@claude-flow/claims/src/application/claim-service.ts:370
);
await this.eventStore.append(statusEvent);
}
async acceptHandoff(issueId: string, claimant: Claimant): Promise<void> {
const claim = await this.claimRepository.findByIssueId(issueId);
// Validate claim exists
if (!claim) {
throw new ClaimOperationError('NOT_CLAIMED', `Issue ${issueId} is not claimed`);
}
// Find pending handoff for this claimant
const pendingHandoff = claim.handoffChain?.find(
(h) => h.status === 'pending' && h.to.id === claimant.id
);
if (!pendingHandoff) {
throw new ClaimOperationError(
'HANDOFF_NOT_FOUND',
`No pending handoff found for claimant ${claimant.name}`
);
}
// Check claimant's workload
const currentClaimCount = await this.claimRepository.countByClaimant(claimant.id);
const maxClaims = claimant.maxConcurrentClaims ?? 5;
if (currentClaimCount >= maxClaims) {
throw new ClaimOperationError(
'MAX_CLAIMS_EXCEEDED',
`Cannot accept handoff: claimant ${claimant.name} at max capacity`
);
}
// Update handoff record
pendingHandoff.status = 'accepted';
pendingHandoff.resolvedAt = new Date();View on GitHub (pinned to 6b01dc5a68)
Solutions
- Pass the exact Claimant object (matching id) that was the 'to' target of the original requestHandoff call.
- Inspect claim.handoffChain via getIssueStatus and confirm a pending entry exists for claimant.id before accepting.
- If the handoff was already accepted, treat the operation as a no-op success rather than re-accepting.
- If the prior handoff was rejected, request a new handoff via requestHandoff instead of accepting.
Example fix
// before
await claimService.acceptHandoff(issueId, { id: agentRef.code, ... }); // wrong id scheme
// after
const target = handoffRecord.to; // the claimant recorded at request time
await claimService.acceptHandoff(issueId, target); Defensive patterns
Strategy: validation
Validate before calling
const status = await claimService.getIssueStatus(issueId);
const pending = status.claim?.handoffChain?.find(
h => h.status === 'pending' && h.to.id === claimant.id
);
if (!pending) {
throw new Error(`No pending handoff targets ${claimant.id}`);
}
await claimService.acceptHandoff(issueId, claimant); Type guard
function hasPendingHandoffFor(claim: IssueClaim | null, claimantId: string): claim is IssueClaim {
return !!claim && !!claim.handoffChain?.some(h => h.status === 'pending' && h.to.id === claimantId);
} Try / catch
try {
await claimService.acceptHandoff(issueId, claimant);
} catch (e) {
if (e instanceof ClaimOperationError && e.code === 'HANDOFF_NOT_FOUND') {
// already resolved or targets someone else; log and move on
return;
}
throw e;
} Prevention
- Carry the exact target Claimant object with the handoff notification instead of reconstructing it.
- Key claimants by a stable, canonical id and normalize at all boundaries.
- Make accept/reject idempotent on the caller side by tracking resolved handoff ids.
When it happens
Trigger: Passing a claimant whose id differs from the handoff's to.id; calling acceptHandoff after the pending entry was already resolved (accepted/rejected); the handoff was cancelled; only a release (not a handoff) was performed on the claim.
Common situations: Agent id format mismatch between the agent registry and the Claimant object used at request time; concurrent accept by two workers that both consumed the same notification; stale notification replayed after the first accept resolved the handoff.
Related errors
- HANDOFF_PENDING
- CLAIMANT_NOT_FOUND
- MAX_CLAIMS_EXCEEDED
- Invalid target format: ${target}. Use agent:<id> or human:<i
- Issue not found: ${input.issueId}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/97581530529bee8f.
Report an issue: GitHub.