{"record":{"id":"34fcc3807e425a6a","repo":"slackhq/nebula","slug":"errunknownstate","errorCode":"ErrUnknownState","errorMessage":"nebula state is invalid","messagePattern":"nebula state is invalid","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"control.go","lineNumber":30,"sourceCode":"\n\t\"github.com/slackhq/nebula/cert\"\n\t\"github.com/slackhq/nebula/header\"\n\t\"github.com/slackhq/nebula/overlay\"\n)\n\ntype RunState int\n\nconst (\n\tStateUnknown RunState = iota\n\tStateReady\n\tStateStarted\n\tStateStopping\n\tStateStopped\n)\n\nvar ErrAlreadyStarted = errors.New(\"nebula is already started\")\nvar ErrAlreadyStopped = errors.New(\"nebula cannot be restarted\")\nvar ErrUnknownState = errors.New(\"nebula state is invalid\")\n\n// Every interaction here needs to take extra care to copy memory and not return or use arguments \"as is\" when touching\n// core. This means copying IP objects, slices, de-referencing pointers and taking the actual value, etc\n\ntype controlEach func(h *HostInfo)\n\ntype controlHostLister interface {\n\tQueryVpnAddr(vpnAddr netip.Addr) *HostInfo\n\tForEachIndex(each controlEach)\n\tForEachVpnAddr(each controlEach)\n\tGetPreferredRanges() []netip.Prefix\n}\n\ntype Control struct {\n\tstateLock sync.Mutex\n\tstate     RunState\n\n\tf                      *Interface","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/control.go#L12-L48","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nc := &control.Control{} // zero value\nc.Start() // ErrUnknownState\n\n// after\nc := control.New(lightHouse, ..., config)\nc.Start()","handlingStrategy":"validation","validationCode":"if c.State() != control.StateReady {\n    return fmt.Errorf(\"control in unexpected state %v; expected StateReady before Start\", c.State())\n}","typeGuard":"func stateIsValid(s control.State) bool {\n    return s >= control.StateReady && s <= control.StateStopped\n}","tryCatchPattern":"if err := c.Start(); err != nil {\n    if errors.Is(err, control.ErrUnknownState) {\n        log.Fatalf(\"control state machine invalid (state=%v); rebuild instance\", c.State())\n    }\n    return err\n}","preventionTips":["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."],"tags":["lifecycle","state-machine","concurrency","go"],"backgroundTag":"invalid-state-error","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}