hashicorp/nomad · error

CSI.ControllerDetachVolume: VolumeID is required

Error message

CSI.ControllerDetachVolume: VolumeID is required

What it means

ControllerDetachVolume validates the detach request before forwarding it to the CSI controller plugin. An empty VolumeID returns this error. Like the attach checks, it is a defensive development aid and should not occur on a properly functioning cluster.

Source

Thrown at client/csi_endpoint.go:155

// the storage node provided in the request.
func (c *CSI) ControllerDetachVolume(req *structs.ClientCSIControllerDetachVolumeRequest, resp *structs.ClientCSIControllerDetachVolumeResponse) error {
	defer metrics.MeasureSince([]string{"client", "csi_controller", "unpublish_volume"}, time.Now())
	plugin, err := c.findControllerPlugin(req.PluginID)
	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) {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set req.VolumeID to the CSI volume ID before calling
  2. Skip the controller detach if the volume ID is unknown (nothing to detach) and clean up local state instead
  3. Trace why the volume record disappeared — check for races between GC and detach

Example fix

// before
req := &structs.ControllerDetachVolumeRequest{ClientCSINodeID: nodeID, ExternalID: extID}
// after
if volID == "" { return nil // already unregistered, skip controller detach }
req := &structs.ControllerDetachVolumeRequest{VolumeID: volID, ClientCSINodeID: nodeID, ExternalID: extID}
Defensive patterns

Strategy: validation

Validate before calling

if req == nil || req.VolumeID == "" {
    return errors.New("ControllerDetachVolume requires a non-empty VolumeID")
}

Type guard

func detachable(req *structs.ControllerDetachVolumeRequest) bool {
    return req != nil && req.VolumeID != ""
}

Try / catch

if err := client.ControllerDetachVolume(req, &resp); err != nil {
    if strings.Contains(err.Error(), "VolumeID is required") {
        return nil // nothing to detach; volume already gone
    }
    return err
}

Prevention

When it happens

Trigger: Calling the client's ControllerDetachVolume RPC with req.VolumeID == "" — e.g. detaching from a claim whose volume record is missing, or constructing the request manually.

Common situations: Volume already purged from state store while detach is still scheduled; unmount/node-unclaim flows that never resolved the volume ID; test harness code.

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