ruvnet/ruflo · error
Cannot vote on amendment with status: ${amendment.status}
Error message
Cannot vote on amendment with status: ${amendment.status} What it means
voteOnAmendment() requires the amendment's status to still be 'proposed'. resolveAmendment() flips status to 'approved' or 'rejected' while keeping the entry in the map, so any vote arriving after resolution throws this error echoing the current status. Duplicate votes by the same voterId are NOT the trigger — the votes Map simply overwrites — this is purely a lifecycle-state violation.
Source
Thrown at v3/@claude-flow/guidance/src/meta-governance.ts:394
status: 'proposed',
votes: new Map(),
...proposal,
};
this.amendments.set(amendment.id, amendment);
return amendment;
}
/**
* Vote on an amendment
*/
voteOnAmendment(amendmentId: string, voterId: string, approve: boolean): void {
const amendment = this.amendments.get(amendmentId);
if (!amendment) {
throw new Error(`Amendment not found: ${amendmentId}`);
}
if (amendment.status !== 'proposed') {
throw new Error(`Cannot vote on amendment with status: ${amendment.status}`);
}
amendment.votes.set(voterId, approve);
}
/**
* Resolve an amendment (check if supermajority reached)
*/
resolveAmendment(amendmentId: string): Amendment {
const amendment = this.amendments.get(amendmentId);
if (!amendment) {
throw new Error(`Amendment not found: ${amendmentId}`);
}
if (amendment.status !== 'proposed') {
throw new Error(`Amendment already resolved: ${amendment.status}`);
}
const totalVotes = amendment.votes.size;View on GitHub (pinned to fa13ee4ad6)
Solutions
- Collect all votes before calling resolveAmendment(); resolution is the cutoff
- In concurrent flows, re-check getPendingAmendments() immediately before each vote and skip non-pending IDs
- Handle 'Cannot vote on amendment with status' as a benign late-vote case rather than a hard failure
- Cache the Amendment returned by resolveAmendment() as the authoritative verdict instead of re-voting
Example fix
// before
const result = governor.resolveAmendment(id);
// ...later, a straggler vote arrives:
governor.voteOnAmendment(id, 'voter-9', true); // throws: status approved
// after
if (governor.getPendingAmendments().some(a => a.id === id)) {
governor.voteOnAmendment(id, 'voter-9', true);
} else {
logger.info('Vote window closed for amendment', { id });
} Defensive patterns
Strategy: validation
Validate before calling
if (!governor.getPendingAmendments().some(a => a.id === amendmentId)) {
logger.info('Vote dropped: amendment no longer proposed', { amendmentId });
return;
}
governor.voteOnAmendment(amendmentId, voterId, approve); Type guard
function isStillProposed(governor: MetaGovernor, id: string): boolean {
return governor.getPendingAmendments().some(a => a.id === id);
} Try / catch
try {
governor.voteOnAmendment(amendmentId, voterId, approve);
} catch (err) {
if (err instanceof Error && err.message.startsWith('Cannot vote on amendment with status')) {
return; // late vote after resolution: benign
}
throw err;
} Prevention
- Resolve only after all expected voters have voted (or a timeout makes late votes droppable)
- In concurrent voters, re-check getPendingAmendments() immediately before each vote
- Duplicate votes by the same voterId are safe; only lifecycle state throws
When it happens
Trigger: Calling voteOnAmendment() after resolveAmendment() already ran; concurrent workers where one resolves while another is still collecting votes; retry queues re-delivering a vote after the coordinator resolved.
Common situations: Fan-out voting where the resolver fires on quorum while stragglers vote; at-least-once message redelivery; sequential test code that resolves in a helper then votes again.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot enact amendment with status: ${amendment.status}
- Amendment not found: ${amendmentId}
- Amendment already resolved: ${amendment.status}
- Amendment rate limit exceeded: ${this.maxAmendmentsPerWindow
- Cannot modify immutable invariant: ${change.target}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/8faffb1726509083.
Report an issue: GitHub.