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 bool

View on GitHub (pinned to 24529f1404)

Solutions

  1. Populate s.Nodes with the node(s) originally registered (including correct node.Id) before calling Deregister.
  2. Fetch the current service via the registry (GetService) to get a Nodes-populated value, then deregister it.
  3. Skip the deregistration call when len(s.Nodes)==0 instead of invoking it.
  4. Ensure node.Id matches the ID used at registration, otherwise Consul deregisters nothing even with nodes set.

Example fix

// before
reg.Deregister(&registry.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

When it happens

Trigger: Calling Deregister with a *registry.Service constructed by hand (e.g. &registry.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


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/5dab94ef6c1948ea. Report an issue: GitHub.