ruvnet/ruflo · warning · Error

No contest to resolve

Error message

No contest to resolve

What it means

Thrown by resolveContest when the Claim exists but its contestInfo field is null/undefined. A contest must be opened first (producing a ContestInfo record on the claim) before anyone can resolve it. This guard runs after the not-found check, so it only fires for claims that exist but were never contested.

Source

Thrown at v3/@claude-flow/claims/src/application/work-stealing-service.ts:410

    await this.emitContestEvent(claim);
  }

  // ===========================================================================
  // Resolve Contest
  // ===========================================================================

  /**
   * Resolve a contest (queen or human decides the winner)
   */
  async resolveContest(issueId: IssueId, winner: Claimant, reason: string): Promise<void> {
    const claim = await this.repository.findByIssueId(issueId);

    if (!claim) {
      throw new Error(`Claim not found for issue: ${issueId}`);
    }

    if (!claim.contestInfo) {
      throw new Error('No contest to resolve');
    }

    if (claim.contestInfo.resolution) {
      throw new Error('Contest has already been resolved');
    }

    const now = new Date();
    const resolvedBy = this.determineResolver(winner, claim.contestInfo);

    // Create resolution
    const resolution: ContestResolution = {
      resolvedAt: now,
      winner,
      resolvedBy,
      reason,
    };

    claim.contestInfo.resolution = resolution;

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Before resolving, read the claim and confirm claim.contestInfo is present; if absent, treat the claim as uncontested and proceed with normal flow.
  2. Trace upstream: verify the contest-creation step (the call that attaches contestInfo) ran successfully and persisted before resolveContest.
  3. Gate resolveContest behind a helper like canResolveContest(claim) that checks both existence and contestInfo, so callers cannot reach the resolver without a contest.
  4. If contention is optional by design, consider a distinct API for uncontested claims rather than reusing resolveContest.

Example fix

// before
await service.resolveContest(issueId, winner, reason);

// after
const claim = await service.repository.findByIssueId(issueId);
if (claim && claim.contestInfo) {
  await service.resolveContest(issueId, winner, reason);
} else {
  logger.info({ issueId }, 'uncontested claim; nothing to resolve');
}
Defensive patterns

Strategy: validation

Validate before calling

const claim = await service.repository.findByIssueId(issueId);
if (claim && !claim.contestInfo) {
  // uncontested: take the non-contest path
  return;
}
if (claim) await service.resolveContest(issueId, winner, reason);

Type guard

function isContested(c: Claim | null | undefined): c is Claim & { contestInfo: ContestInfo } {
  return !!c && !!c.contestInfo;
}

Try / catch

try {
  await service.resolveContest(issueId, winner, reason);
} catch (e) {
  if (e instanceof Error && /No contest to resolve/.test(e.message)) return;
  throw e;
}

Prevention

When it happens

Trigger: Calling resolveContest on a claim that is active/owned normally but has no competing claimant that triggered a contest. It happens when a caller treats any claim as resolvable rather than only contested ones, or when the contest-opening path was skipped or failed silently before resolution.

Common situations: Automation that always attempts to resolve after a timeout regardless of whether a contest occurred; a partial write where the claim was created but contestInfo was never attached due to an earlier exception; refactors that changed how contests are recorded without updating the resolver call sites.

Related errors


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