hashicorp/nomad · error

missing node ID for client deregistration

Error message

missing node ID for client deregistration

What it means

Guard in Node.Deregister: the deregistration request carries an empty NodeID, so there is no client node to remove from state.

Source

Thrown at nomad/node_endpoint.go:531

func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.NodeUpdateResponse) error {
	authErr := n.srv.Authenticate(n.ctx, args)
	if done, err := n.srv.forward("Node.Deregister", args, args, reply); done {
		return err
	}
	n.srv.MeasureRPCRate("node", structs.RateMetricWrite, args)
	if authErr != nil {
		return structs.ErrPermissionDenied
	}
	defer metrics.MeasureSince([]string{"nomad", "client", "deregister"}, time.Now())

	if aclObj, err := n.srv.ResolveACL(args); err != nil {
		return structs.ErrPermissionDenied
	} else if !aclObj.AllowNodeWrite() {
		return structs.ErrPermissionDenied
	}

	if args.NodeID == "" {
		return fmt.Errorf("missing node ID for client deregistration")
	}

	// deregister takes a batch
	repack := &structs.NodeBatchDeregisterRequest{
		NodeIDs:      []string{args.NodeID},
		WriteRequest: args.WriteRequest,
	}

	return n.deregister(repack, reply, func() (any, uint64, error) {
		return n.srv.raftApply(structs.NodeDeregisterRequestType, args)
	})
}

// BatchDeregister is used to remove client nodes from the cluster.
func (n *Node) BatchDeregister(args *structs.NodeBatchDeregisterRequest, reply *structs.NodeUpdateResponse) error {
	authErr := n.srv.Authenticate(n.ctx, args)
	if done, err := n.srv.forward("Node.BatchDeregister", args, args, reply); done {
		return err

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Populate args.NodeID with the target node's UUID before calling Deregister
  2. Look up the node by name via the node list API to obtain its ID first
  3. Guard the call site: skip/flag nodes whose ID is empty
  4. Use BatchDeregister with a filtered non-empty ID list if deregistering many

Example fix

// before
req := &api.NodeDeregisterRequest{NodeID: nodeIDFromInventory}
// after
if nodeIDFromInventory == "" {
    return fmt.Errorf("skip: empty node id")
}
req := &api.NodeDeregisterRequest{NodeID: nodeIDFromInventory}
Defensive patterns

Strategy: validation

Validate before calling

if nodeID == "" {
    return fmt.Errorf("refusing to deregister: empty node id")
}

Type guard

func hasNodeID(id string) bool { return id != "" }

Try / catch

err := client.Nodes().Deregister(nodeID, nil)
if err != nil && strings.Contains(err.Error(), "missing node ID") {
    // fix caller's data pipeline; no server action needed
}

Prevention

When it happens

Trigger: RPC call to Node.Deregister with NodeID set to ""; callers building requests programmatically and leaving NodeID unset after a failed lookup.

Common situations: Scripts deregistering nodes from stale inventories where the node id column is empty; automation passing an unbound variable into the request struct.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/9e12e44bd3c46f1b. Report an issue: GitHub.