micro/go-micro · error
require at least one node
Error message
require at least one node
What it means
consulRegistry.Deregister requires the registry.Service being deregistered to carry at least one node, because deregistration is performed per node ID against the Consul agent. A service with zero nodes has nothing identifiable to remove, so the call is rejected up front.
Source
Thrown at registry/consul/consul.go:156
// set the config
c.config = config
// remove client
c.client = nil
// setup the client
c.Client()
}
func (c *consulRegistry) Init(opts ...registry.Option) error {
configure(c, opts...)
return nil
}
func (c *consulRegistry) Deregister(s *registry.Service, opts ...registry.DeregisterOption) error {
if len(s.Nodes) == 0 {
return errors.New("require at least one node")
}
// delete our hash and time check of the service
c.Lock()
delete(c.register, s.Name)
delete(c.lastChecked, s.Name)
c.Unlock()
node := s.Nodes[0]
return c.Client().Agent().ServiceDeregister(node.Id)
}
func (c *consulRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
if len(s.Nodes) == 0 {
return errors.New("require at least one node")
}
var regTCPCheck boolView on GitHub (pinned to 24529f1404)
Solutions
- Populate s.Nodes with the node(s) originally registered (including correct node.Id) before calling Deregister.
- Fetch the current service via the registry (GetService) to get a Nodes-populated value, then deregister it.
- Skip the deregistration call when len(s.Nodes)==0 instead of invoking it.
- Ensure node.Id matches the ID used at registration, otherwise Consul deregisters nothing even with nodes set.
Example fix
// before
reg.Deregister(®istry.Service{Name: "foo"}) // panics into error: no nodes
// after
svcs, _ := reg.GetService("foo")
for _, s := range svcs {
reg.Deregister(s)
} Defensive patterns
Strategy: validation
Validate before calling
func canDeregister(s *registry.Service) bool {
return s != nil && s.Name != "" && len(s.Nodes) > 0
} Type guard
func hasNodes(s *registry.Service) bool { return s != nil && len(s.Nodes) > 0 } Try / catch
if err := reg.Deregister(svc); err != nil && strings.Contains(err.Error(), "require at least one node") {
log.Warn("skipping deregister: service has no nodes", "service", svc.Name)
return nil
} else if err != nil { return err } Prevention
- Always deregister with the full Service object from GetService, not a hand-built one
- Keep node.Id from registration intact through shutdown paths
- Skip deregistration when Nodes is empty and log instead
- Add a unit test asserting Nodes are preserved in your shutdown flow
When it happens
Trigger: Calling Deregister with a *registry.Service constructed by hand (e.g. ®istry.Service{Name:"foo"}) with an empty Nodes slice; passing a service obtained from a source that stripped nodes.
Common situations: Shutdown handlers deregistering a locally built service struct; tests reusing a Service value whose Nodes were cleared; deserialization of service metadata that omitted nodes.
Related errors
- no service name configured
- no service node configured
- require at least one node
- ai model is nil
- ErrMissingTopic
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/5dab94ef6c1948ea.
Report an issue: GitHub.