hashicorp/nomad · error

CSI.NodeDetachVolume: PluginID is required

Error message

CSI.NodeDetachVolume: PluginID is required

What it means

NodeDetachVolume on the client validates that the request names the CSI plugin (PluginID) that manages the volume. The plugin ID selects which plugin manager handles the detach. An empty PluginID fails this defensive check before any plugin call.

Source

Thrown at client/csi_endpoint.go:507

		resp.Entries = append(resp.Entries, snap)
		if req.MaxEntries != 0 && int32(len(resp.Entries)) == req.MaxEntries {
			break
		}
	}

	return nil
}

// NodeDetachVolume is used to detach a volume from a CSI Cluster from
// the storage node provided in the request.
func (c *CSI) NodeDetachVolume(req *structs.ClientCSINodeDetachVolumeRequest, resp *structs.ClientCSINodeDetachVolumeResponse) error {
	defer metrics.MeasureSince([]string{"client", "csi_node", "detach_volume"}, time.Now())

	// The following block of validation checks should not be reached on a
	// real Nomad cluster. They serve as a defensive check before forwarding
	// requests to plugins, and to aid with development.
	if req.PluginID == "" {
		return errors.New("CSI.NodeDetachVolume: PluginID is required")
	}
	if req.VolumeID == "" {
		return errors.New("CSI.NodeDetachVolume: VolumeID is required")
	}
	if req.AllocID == "" {
		return errors.New("CSI.NodeDetachVolume: AllocID is required")
	}

	ctx, cancelFn := c.requestContext()
	defer cancelFn()

	manager, err := c.c.csimanager.ManagerForPlugin(ctx, req.PluginID)
	if err != nil {
		return fmt.Errorf("CSI.NodeDetachVolume: %v", err)
	}

	usageOpts := &csimanager.UsageOptions{
		ReadOnly:       req.ReadOnly,

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set req.PluginID to the volume's CSI plugin ID (from structs.CSIVolume.PluginID)
  2. Resolve the plugin ID from the volume record in state store before detaching
  3. If hit in production, verify the volume was registered with a plugin ID and that state wasn't corrupted

Example fix

// before
req := &structs.NodeDetachVolumeRequest{VolumeID: volID, AllocID: allocID}
// after
req := &structs.NodeDetachVolumeRequest{PluginID: vol.PluginID, VolumeID: volID, AllocID: allocID}
if req.PluginID == "" { return fmt.Errorf("volume %q has no plugin ID", volID) }
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

func detachNodeReady(req *structs.NodeDetachVolumeRequest) bool {
    return req != nil && req.PluginID != "" && req.VolumeID != "" && req.AllocID != ""
}

Try / catch

if err := client.NodeDetachVolume(req, &resp); err != nil {
    if strings.Contains(err.Error(), "PluginID is required") {
        return fmt.Errorf("volume %q lacks plugin ID; check registration state", req.VolumeID)
    }
    return err
}

Prevention

When it happens

Trigger: Calling the client's NodeDetachVolume RPC with req.PluginID == "" — e.g. building the request from a volume whose plugin ID field was never hydrated, or dropping it in a hand-rolled request.

Common situations: Volume structs missing PluginID after upgrade/state migration; test code constructing NodeDetachVolumeRequest manually; claim reconciliation on partially restored volumes.

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