ruvnet/ruflo · error · Error
Only leader can propose values
Error message
Only leader can propose values
What it means
RaftConsensus.propose() requires this.node.state === 'leader'; followers and candidates throw. Leadership comes only from Raft election, and the ConsensusEngine facade forwards the 'leader.elected' event and exposes isLeader()/getLeaderId() so callers can check before proposing.
Source
Thrown at v3/@claude-flow/swarm/src/consensus/raft.ts:181
addPeer(peerId: string): void {
this.peers.set(peerId, {
id: peerId,
state: 'follower',
currentTerm: 0,
log: [],
commitIndex: 0,
lastApplied: 0,
});
}
removePeer(peerId: string): void {
this.peers.delete(peerId);
}
async propose(value: unknown): Promise<ConsensusProposal> {
if (this.node.state !== 'leader') {
throw new Error('Only leader can propose values');
}
this.proposalCounter++;
const proposalId = `raft_${this.node.id}_${this.proposalCounter}`;
const proposal: ConsensusProposal = {
id: proposalId,
proposerId: this.node.id,
value,
term: this.node.currentTerm,
timestamp: new Date(),
votes: new Map(),
status: 'pending',
};
// Add to local log
const logEntry: RaftLogEntry = {
term: this.node.currentTerm,View on GitHub (pinned to fa13ee4ad6)
Solutions
- Wait for the 'leader.elected' event, then propose only from the leader (check engine.isLeader()).
- Forward values to the current leader (engine.getLeaderId()) when this node is a follower.
- Retry the proposal after an election timeout — leadership may have moved to this node.
- In tests, drive the election explicitly before asserting propose() succeeds.
Example fix
// before — propose from any node
await raft.propose(value); // throws unless this node is leader
// after — gate on leadership
let leaderId: string | undefined;
engine.on('leader.elected', ({ leaderId: id }) => { leaderId = id; });
if (engine.isLeader()) {
await raft.propose(value);
} else {
await forwardToLeader(leaderId, value);
} Defensive patterns
Strategy: validation
Validate before calling
// the facade exposes leadership explicitly
if (!engine.isLeader()) {
const leaderId = engine.getLeaderId();
if (!leaderId) throw new Error('no leader elected yet');
await forwardToLeader(leaderId, value);
} else {
await engine.propose(value);
} Try / catch
try {
await raft.propose(value);
} catch (e) {
if (e instanceof Error && e.message === 'Only leader can propose values') {
// wait for the next 'leader.elected' event and retry, or forward to the leader
} else {
throw e;
}
} Prevention
- Check engine.isLeader() (or cache leaderId from 'leader.elected') before every propose.
- Centralize proposals in a leader-aware router instead of proposing from all replicas.
- Expect leadership churn: retry proposals after elections instead of treating them as fatal.
When it happens
Trigger: Proposing before the election has finished (node is 'follower' or 'candidate'); proposing after this node lost leadership through a term change or partition; every node in a test cluster proposing simultaneously instead of only the elected leader.
Common situations: Symmetric deployments where each replica proposes on a timer; leadership churn under network partitions; single-node tests where election was never triggered; proposals routed to a stale leader id.
Related errors
- Only primary can propose values
- Proposal ${proposalId} not found
- Proposal ${proposalId} not found
- Proposal ${proposalId} already resolved
- Proposal not found: ${proposalId}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/f057dad85d1d98d7.
Report an issue: GitHub.