micro/go-micro · error
require at least one node
Error message
require at least one node
What it means
etcdRegistry.registerNode performs the same guard: it refuses to register a node when the parent service's Nodes slice is empty, because lease keys are derived from service name + node ID. It is invoked from Register for each node and fails fast on an empty set.
Source
Thrown at registry/etcd/etcd.go:161
node := strings.ReplaceAll(id, "/", "-")
return path.Join(prefix, service, node)
}
func servicePath(s string) string {
return path.Join(prefix, strings.ReplaceAll(s, "/", "-"))
}
func (e *etcdRegistry) Init(opts ...registry.Option) error {
return configure(e, opts...)
}
func (e *etcdRegistry) Options() registry.Options {
return e.options
}
func (e *etcdRegistry) registerNode(s *registry.Service, node *registry.Node, opts ...registry.RegisterOption) error {
if len(s.Nodes) == 0 {
return errors.New("require at least one node")
}
// check existing lease cache
e.RLock()
leaseID, ok := e.leases[s.Name+node.Id]
e.RUnlock()
log := e.options.Logger
if !ok {
// missing lease, check if the key exists
ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout)
defer cancel()
// look for the existing key
rsp, err := e.client.Get(ctx, nodePath(s.Name, node.Id), clientv3.WithSerializable())
if err != nil {
return errView on GitHub (pinned to 24529f1404)
Solutions
- Ensure Register is always called with at least one populated *registry.Node.
- Guard your registration wrapper: only call Register when len(s.Nodes) > 0.
- Verify node construction (address, port, id) from config isn't silently failing.
- Check for concurrent code that truncates/reassigns the Nodes slice before Register.
Example fix
// before
if len(svc.Nodes) == 0 { /* bug: register anyway */ }
reg.Register(svc)
// after
if len(svc.Nodes) == 0 {
return fmt.Errorf("service %q has no nodes; not registering", svc.Name)
}
reg.Register(svc) Defensive patterns
Strategy: validation
Validate before calling
func registerSafe(s *registry.Service) error {
if len(s.Nodes) == 0 {
return fmt.Errorf("refusing to register service %q with no nodes", s.Name)
}
return nil
}
// call registerSafe(s) before reg.Register(s) Type guard
func hasNodes(s *registry.Service) bool { return s != nil && len(s.Nodes) > 0 } Try / catch
if err := reg.Register(svc); err != nil && strings.Contains(err.Error(), "require at least one node") {
log.Error("etcd register rejected: empty node list", "service", svc.Name)
return err
} Prevention
- Ensure every Register call includes at least one populated Node
- Check that node-building code isn't silently skipping nodes on missing config
- Avoid concurrent mutation of the Nodes slice before Register
- Test the register path with a service fixture containing exactly one node
When it happens
Trigger: Register called with an empty Nodes slice reaches registerNode and returns this error; also possible when Register builds the node list dynamically and the loop that adds nodes never executes.
Common situations: Programmatic registration code with an empty node list; configuration missing address/port so node construction is skipped; race where node list is reset before Register is invoked.
Related errors
- no service name configured
- no service node configured
- require at least one node
- could not get next
- ai model is nil
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/7f4cf4386ee95cb4.
Report an issue: GitHub.