hashicorp/consul · error
ClusterSize not set
Error message
ClusterSize not set
What it means
agent/ae.StateSyncer drives Consul's anti-entropy full/partial sync state machine. Run() requires the ClusterSize callback (used at ae.go:321 to scale the sync rate) to be set; the Consul agent wires it in agent/agent.go:761 to the LAN Serf member count. Calling Run() on a StateSyncer whose ClusterSize field is nil panics immediately — a fail-fast guard against mis-wiring, not a runtime data condition.
Source
Thrown at agent/ae/ae.go:162
// fsmState defines states for the state machine.
type fsmState string
const (
doneState fsmState = "done"
fullSyncState fsmState = "fullSync"
partialSyncState fsmState = "partialSync"
retryFullSyncState fsmState = "retryFullSync"
)
// Run is the long running method to perform state synchronization
// between local and remote servers.
func (s *StateSyncer) Run() {
if s.Disabled() {
return
}
if s.ClusterSize == nil {
panic("ClusterSize not set")
}
s.resetNextFullSyncCh()
s.runFSM(fullSyncState, s.nextFSMState)
}
// runFSM runs the state machine.
func (s *StateSyncer) runFSM(fs fsmState, next func(fsmState) fsmState) {
for {
if fs = next(fs); fs == doneState {
return
}
}
}
// nextFSMState determines the next state based on the current state.
func (s *StateSyncer) nextFSMState(fs fsmState) fsmState {
switch fs {
case fullSyncState:View on GitHub (pinned to 2397ff0d76)
Solutions
- Set the ClusterSize callback before starting the syncer: sync.ClusterSize = func() int { return memberCount() }
- If embedding, mirror the agent wiring (agent/agent.go:761): use your LAN Serf member count, e.g. len(serfLAN.Members())
- In tests, use a fixed value like ClusterSize = func() int { return 1 } (pattern from ae_test.go:413)
Example fix
// before
s := &ae.StateSyncer{Logger: logger, StateStore: store}
go s.Run() // panic: ClusterSize not set
// after
s := &ae.StateSyncer{Logger: logger, StateStore: store}
s.ClusterSize = func() int { return len(serfLAN.Members()) }
go s.Run() Defensive patterns
Strategy: validation
Validate before calling
// before starting the syncer
if syncer.ClusterSize == nil {
return errors.New("ae.StateSyncer.ClusterSize must be set before Run (e.g. len(serfLAN.Members()))")
}
go syncer.Run() Try / catch
// test-harness only: assert the fail-fast contract instead of crashing
func mustRun(s *ae.StateSyncer) (r any) {
defer func() { r = recover() }()
go s.Run()
time.Sleep(50 * time.Millisecond)
return nil
} Prevention
- When embedding ae.StateSyncer, copy the agent's wiring pattern from agent/agent.go:761
- Centralize StateSyncer construction in one factory that sets every required field
- Keep a compile-time checklist of required callbacks (ClusterSize, Logger, StateStore) in the factory
When it happens
Trigger: Constructing ae.StateSyncer directly (unit tests, embedded Consul, custom agent wiring) and calling Run() without setting ClusterSize; refactoring that drops the field assignment; test setup copied from a fixture that omits it (ae_test.go:132 explicitly asserts this panic).
Common situations: Embedding Consul's anti-entropy syncer into another binary; upgrading Consul versions where StateSyncer gained required fields; writing new unit tests that build the struct with only partial fields.
Related errors
- failed to decode request: %v
- failed to decode batch updates: %v
- all indexers must have a non-empty name
- no indexer was supplied when creating a new cache Index
- The Indexer must also implement one of the SingleIndexer or
AI-assisted analysis of hashicorp/consul@2397ff0d76 (2026-08-15).
Data as JSON: /api/errors/8250453c518f5689.
Report an issue: GitHub.