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
- Set req.PluginID to the volume's CSI plugin ID (from structs.CSIVolume.PluginID)
- Resolve the plugin ID from the volume record in state store before detaching
- 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
- Read PluginID from structs.CSIVolume.PluginID, never from caller-supplied strings
- Validate volumes at registration time so PluginID is always set
- Add PluginID to any persisted claim state you replay after restart
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
- CSI.ControllerAttachVolume: VolumeID is required
- CSI.ControllerAttachVolume: ClientCSINodeID is required
- CSI.ControllerDetachVolume: VolumeID is required
- CSI.ControllerDetachVolume: ClientCSINodeID is required
- CSI.NodeDetachVolume: VolumeID is required
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/498b35d886789636.
Report an issue: GitHub.