hashicorp/nomad · error

CSI.ControllerDetachVolume: ClientCSINodeID is required

Error message

CSI.ControllerDetachVolume: ClientCSINodeID is required

What it means

ControllerDetachVolume also requires ClientCSINodeID so the controller plugin can identify which node to detach the volume from. An empty value returns this defensive error before the plugin is contacted.

Source

Thrown at client/csi_endpoint.go:159

	if err != nil {
		// the server's view of the plugin health is stale, so let it know it
		// should retry with another controller instance
		return fmt.Errorf("CSI.ControllerDetachVolume: %w: %v",
			nstructs.ErrCSIClientRPCRetryable, err)
	}
	defer plugin.Close()

	// The following block of validation checks should not be reached on a
	// real Nomad cluster as all of this data should be validated when registering
	// volumes with the cluster. They serve as a defensive check before forwarding
	// requests to plugins, and to aid with development.

	if req.VolumeID == "" {
		return errors.New("CSI.ControllerDetachVolume: VolumeID is required")
	}

	if req.ClientCSINodeID == "" {
		return errors.New("CSI.ControllerDetachVolume: ClientCSINodeID is required")
	}

	csiReq := req.ToCSIRequest()

	// Submit the request for a volume to the CSI Plugin.
	ctx, cancelFn := c.requestContext()
	defer cancelFn()
	// CSI ControllerUnpublishVolume errors for timeout, codes.Unavailable and
	// codes.ResourceExhausted are retried; all other errors are fatal.
	_, err = plugin.ControllerUnpublishVolume(ctx, csiReq,
		grpc_retry.WithPerRetryTimeout(CSIPluginRequestTimeout),
		grpc_retry.WithMax(3),
		grpc_retry.WithBackoff(grpc_retry.BackoffExponential(100*time.Millisecond)))
	if errors.Is(err, nstructs.ErrCSIClientRPCIgnorable) {
		// if the controller detach previously happened but the server failed to
		// checkpoint, we'll get an error from the plugin but can safely ignore it.
		c.c.logger.Debug("could not unpublish volume", "error", err)
		return nil

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Populate ClientCSINodeID with the node's CSI node plugin ID
  2. If the node is gone, treat the detach as best-effort and rely on the controller's unpublish/timeout instead of a per-node detach
  3. Wait for node re-registration if the plugin is temporarily missing

Example fix

// before
req := &structs.ControllerDetachVolumeRequest{VolumeID: volID, ExternalID: extID}
// after
if node.CSINodeID == "" { return errors.New("cannot detach: node CSI plugin ID unknown") }
req := &structs.ControllerDetachVolumeRequest{VolumeID: volID, ClientCSINodeID: node.CSINodeID, ExternalID: extID}
Defensive patterns

Strategy: validation

Validate before calling

if req.ClientCSINodeID == "" {
    return errors.New("ControllerDetachVolume requires ClientCSINodeID")
}

Type guard

func hasCSINodeID(n *structs.Node) bool { return n != nil && n.CSINodeID != "" }

Try / catch

if err := client.ControllerDetachVolume(req, &resp); err != nil {
    if strings.Contains(err.Error(), "ClientCSINodeID is required") {
        return errors.New("node gone or plugin unregistered; relying on controller unpublish timeout")
    }
    return err
}

Prevention

When it happens

Trigger: Calling ControllerDetachVolume without ClientCSINodeID — typically when the node plugin ID was never resolved, or the request was hand-built without it.

Common situations: Node deregistered or its CSI plugin uninstalled before detach; manual RPC construction; passing the wrong identifier field.

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/f1b6aaaee1d8fbe7. Report an issue: GitHub.