ruvnet/ruflo · error

Proposal ${proposalId} not found

Error message

Proposal ${proposalId} not found

What it means

AdversarialCoordinator.vote() registers a voter's approve/reject decision on a memory proposal held in the in-memory `proposals` Map. It throws when `proposalId` does not match any proposal in this instance. Because state is per-instance and in-memory, the most common root cause is an ID from a previous run or a different coordinator object.

Source

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

          oldestTimestamp = proposal.timestamp;
          oldestId = id;
        }
      }
      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

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Always use the exact proposalId returned by `propose()`
  2. Check existence first via the public accessor: `if (coordinator.getProposal(proposalId) === undefined) return;`
  3. If the coordinator was restarted, re-run propose() to mint a fresh proposal and vote on that
  4. In multi-instance setups, route all votes for a proposal to the same coordinator instance that created it

Example fix

// before
coordinator.vote(proposalIdFromLastRun, voterId, true); // throws

// after
const proposal = coordinator.getProposal(proposalIdFromLastRun);
if (proposal && !proposal.resolved) {
  coordinator.vote(proposalIdFromLastRun, voterId, true);
}
Defensive patterns

Strategy: validation

Validate before calling

const proposal = coordinator.getProposal(proposalId);
if (proposal === undefined) {
  // unknown to this instance: skip, re-propose, or log
}

Try / catch

try {
  coordinator.vote(proposalId, voterId, approve);
} catch (err) {
  if (err instanceof Error && err.message.endsWith('not found')) {
    // stale ID (restart / wrong instance) — drop the vote, do not crash the voter
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `coordinator.vote(someId, voterId, true)` where someId was never returned by propose(); using a proposalId captured before a process restart (the Map is not persisted); concatenating or truncating the ID string.

Common situations: Service restarted between proposing and voting, dropping all in-memory proposals; multiple coordinator instances (one per worker) with IDs crossing between them; IDs passed through JSON and mangled; voting on already-garbage-collected proposals in long test runs.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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