ruvnet/ruflo · error · Error

Issue ${issueId} is not claimed by ${this.formatClaimant(cla

Error message

Issue ${issueId} is not claimed by ${this.formatClaimant(claimant)}

What it means

The second guard in release(): the claim exists, but isSameClaimant(claim.claimant, claimant) is false. isSameClaimant compares type plus userId (human) or agentId (agent). The message names the caller's claimant via formatClaimant (human:<name> or agent:<type>:<id>). The claim is not removed.

Source

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

    this.emitEvent({
      type: 'issue:claimed',
      timestamp: now,
      issueId,
      claimant,
      previousClaimant: existing?.claimant,
    });

    return { success: true, claim };
  }

  async release(issueId: string, claimant: Claimant): Promise<void> {
    const claim = this.claims.get(issueId);
    if (!claim) {
      throw new Error(`Issue ${issueId} is not claimed`);
    }

    if (!this.isSameClaimant(claim.claimant, claimant)) {
      throw new Error(`Issue ${issueId} is not claimed by ${this.formatClaimant(claimant)}`);
    }

    this.claims.delete(issueId);
    this.stealableInfo.delete(issueId);
    await this.saveClaims();

    this.emitEvent({
      type: 'issue:released',
      timestamp: new Date(),
      issueId,
      claimant,
    });
  }

  // ==========================================================================
  // Handoffs
  // ==========================================================================

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Have each caller release only with the exact claimant it used to claim.
  2. If a transfer is intended, use the handoff flow (requestHandoff/acceptHandoff) rather than release+claim.
  3. Resolve identity drift: derive agentId from a stable source (not a random per-run id).

Example fix

// before: agent A releases B's claim
await claims.release(issueId, agentA); // claim held by agentB -> throws

// after: only the holder releases, or use handoff
const claim = claims.peek?.(issueId);
if (claim && isSameClaimant(claim.claimant, agentA)) {
  await claims.release(issueId, agentA);
} else if (claim) {
  await claims.requestHandoff(issueId, claim.claimant, agentA, 'rebalance');
}
Defensive patterns

Strategy: validation

Validate before calling

function canRelease(claims, issueId, claimant) {
  const c = claims.list?.().find(x => x.issueId === issueId);
  return !!c && isSameClaimant(c.claimant, claimant);
}

Type guard

function isSameClaimant(a, b): boolean {
  if (a.type !== b.type) return false;
  return a.type === 'human' ? a.userId === b.userId : a.agentId === b.agentId;
}

Prevention

When it happens

Trigger: Agent A trying to release a claim held by agent B; a human trying to release an agent's claim; using a stale claimant object whose agentId changed after a restart; case/whitespace drift in userId.

Common situations: Two agents with overlapping scopes; recovery logic using the wrong identity; tests using a default claimant that does not match the one used in claim(); renaming an agent without updating persisted claimant refs.

Related errors


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