slackhq/nebula · error
ErrUnknownState
ErrUnknownState
Error message
nebula state is invalid
What it means
ErrUnknownState in control.go is returned by Start() when the Control's state is not one of the recognized states in the switch (ready/stopping/stopped/started fall-through default). It indicates the internal state machine reached an invalid or uninitialized value.
Source
Thrown at control.go:30
"github.com/slackhq/nebula/cert"
"github.com/slackhq/nebula/header"
"github.com/slackhq/nebula/overlay"
)
type RunState int
const (
StateUnknown RunState = iota
StateReady
StateStarted
StateStopping
StateStopped
)
var ErrAlreadyStarted = errors.New("nebula is already started")
var ErrAlreadyStopped = errors.New("nebula cannot be restarted")
var ErrUnknownState = errors.New("nebula state is invalid")
// Every interaction here needs to take extra care to copy memory and not return or use arguments "as is" when touching
// core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc
type controlEach func(h *HostInfo)
type controlHostLister interface {
QueryVpnAddr(vpnAddr netip.Addr) *HostInfo
ForEachIndex(each controlEach)
ForEachVpnAddr(each controlEach)
GetPreferredRanges() []netip.Prefix
}
type Control struct {
stateLock sync.Mutex
state RunState
f *InterfaceView on GitHub (pinned to dd8f660c0a)
Solutions
- Ensure the Control is created via its constructor so StateReady is set before Start.
- Serialize Start/Stop calls with a mutex or single goroutine to avoid corrupting the state field.
- Log/inspect c.State() when this error appears to identify the invalid value.
- Report or fix the state transition bug if a legitimately new state bypasses the switch.
Example fix
// before
c := &control.Control{} // zero value
c.Start() // ErrUnknownState
// after
c := control.New(lightHouse, ..., config)
c.Start() Defensive patterns
Strategy: validation
Validate before calling
if c.State() != control.StateReady {
return fmt.Errorf("control in unexpected state %v; expected StateReady before Start", c.State())
} Type guard
func stateIsValid(s control.State) bool {
return s >= control.StateReady && s <= control.StateStopped
} Try / catch
if err := c.Start(); err != nil {
if errors.Is(err, control.ErrUnknownState) {
log.Fatalf("control state machine invalid (state=%v); rebuild instance", c.State())
}
return err
} Prevention
- Always construct Control via its constructor; never use the zero value.
- Protect state transitions with the provided locking or a single owner goroutine.
- Enable -race in tests covering concurrent Start/Stop.
- Log State() on every lifecycle transition for diagnosability.
When it happens
Trigger: Start() invoked when c.state holds an unexpected value (control.go:85 default branch) — e.g. an uninitialized Control or a state value corrupted by concurrent mutation without the proper locking.
Common situations: Using a zero-value Control without proper construction; data races on the state field from concurrent Start/Stop calls in custom code; version mismatches where a new state was added but Start's switch is stale.
Related errors
- ErrAlreadyStarted
- ErrAlreadyStopped
- queue set already closed
- queue set already closed
- ErrTruncatedPEMBlock
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/34fcc3807e425a6a.
Report an issue: GitHub.