ruvnet/ruflo · warning

Proposal ${proposalId} already resolved

Error message

Proposal ${proposalId} already resolved

What it means

AdversarialCoordinator.vote() refuses votes on proposals already marked `resolved` (set by resolve()). This keeps the final quorum decision immutable after resolution. The check runs after the existence check, so seeing this error means the ID is valid but voting is closed.

Source

Thrown at v3/@claude-flow/guidance/src/adversarial.ts:699

      }
      if (oldestId) {
        this.proposals.delete(oldestId);
      }
    }

    return proposalId;
  }

  /**
   * Vote on a proposal
   */
  vote(proposalId: string, voterId: string, approve: boolean): void {
    const proposal = this.proposals.get(proposalId);
    if (!proposal) {
      throw new Error(`Proposal ${proposalId} not found`);
    }
    if (proposal.resolved) {
      throw new Error(`Proposal ${proposalId} already resolved`);
    }

    proposal.votes.set(voterId, approve);
  }

  /**
   * Resolve a proposal (check if quorum reached)
   */
  resolve(proposalId: string): QuorumResult {
    const proposal = this.proposals.get(proposalId);
    if (!proposal) {
      throw new Error(`Proposal ${proposalId} not found`);
    }

    // Single pass over votes instead of two filter calls
    let forCount = 0;
    let againstCount = 0;
    for (const v of proposal.votes.values()) {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Check `coordinator.getProposal(id)?.resolved` before voting and skip closed proposals
  2. Treat this error as a no-op signal in retry/replay logic (catch and drop the duplicate vote)
  3. Propose a new proposal if a post-resolution vote genuinely must be recorded

Example fix

// before
coordinator.vote(proposalId, voterId, true); // may throw 'already resolved'

// after
const p = coordinator.getProposal(proposalId);
if (p && !p.resolved) {
  coordinator.vote(proposalId, voterId, true);
} else {
  // voting closed; duplicate or late vote — drop it
}
Defensive patterns

Strategy: validation

Validate before calling

const p = coordinator.getProposal(proposalId);
if (p && !p.resolved) {
  coordinator.vote(proposalId, voterId, approve);
}

Try / catch

try {
  coordinator.vote(proposalId, voterId, approve);
} catch (err) {
  if (err instanceof Error && err.message.includes('already resolved')) {
    // late/duplicate vote after quorum — safe to ignore
  } else throw err;
}

Prevention

When it happens

Trigger: Calling vote() after resolve(proposalId) already ran; two voters racing where one voter triggers resolution and the second vote lands after; retry pipelines that replay all votes including ones already counted toward a resolved proposal.

Common situations: Fan-out voting where late voters straggle in after quorum was reached; event replay / at-least-once delivery re-delivering a vote; test suites that reuse a proposal across cases without recreating it.

Related errors


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