{"record":{"id":"5282b838aba89601","repo":"hashicorp/nomad","slug":"csi-nodedetachvolume-volumeid-is-required","errorCode":null,"errorMessage":"CSI.NodeDetachVolume: VolumeID is required","messagePattern":"CSI\\.NodeDetachVolume: VolumeID is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/csi_endpoint.go","lineNumber":510,"sourceCode":"\t\t}\n\t}\n\n\treturn nil\n}\n\n// NodeDetachVolume is used to detach a volume from a CSI Cluster from\n// the storage node provided in the request.\nfunc (c *CSI) NodeDetachVolume(req *structs.ClientCSINodeDetachVolumeRequest, resp *structs.ClientCSINodeDetachVolumeResponse) error {\n\tdefer metrics.MeasureSince([]string{\"client\", \"csi_node\", \"detach_volume\"}, time.Now())\n\n\t// The following block of validation checks should not be reached on a\n\t// real Nomad cluster. They serve as a defensive check before forwarding\n\t// requests to plugins, and to aid with development.\n\tif req.PluginID == \"\" {\n\t\treturn errors.New(\"CSI.NodeDetachVolume: PluginID is required\")\n\t}\n\tif req.VolumeID == \"\" {\n\t\treturn errors.New(\"CSI.NodeDetachVolume: VolumeID is required\")\n\t}\n\tif req.AllocID == \"\" {\n\t\treturn errors.New(\"CSI.NodeDetachVolume: AllocID is required\")\n\t}\n\n\tctx, cancelFn := c.requestContext()\n\tdefer cancelFn()\n\n\tmanager, err := c.c.csimanager.ManagerForPlugin(ctx, req.PluginID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"CSI.NodeDetachVolume: %v\", err)\n\t}\n\n\tusageOpts := &csimanager.UsageOptions{\n\t\tReadOnly:       req.ReadOnly,\n\t\tAttachmentMode: req.AttachmentMode,\n\t\tAccessMode:     req.AccessMode,\n\t}","sourceCodeStart":492,"sourceCodeEnd":528,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/csi_endpoint.go#L492-L528","documentation":"NodeDetachVolume requires the VolumeID so the plugin manager can find the volume usage / detach the right volume on the node. An empty VolumeID returns this defensive error before contacting the plugin.","triggerScenarios":"Calling NodeDetachVolume with req.VolumeID == \"\" — e.g. when the claim's volume ID was lost or the caller only had the allocation and forgot to resolve the volume.","commonSituations":"Unmount paths iterating allocations without carrying the volume ID; volume deregistered concurrently; manual test RPCs.","solutions":["Set req.VolumeID from the allocation's volume claim (alloc.AllocatedResources.VolumeClaims[].VolumeID)","Skip the detach when no claim exists (nothing was mounted)","Check for races between claim GC and detach"],"exampleFix":"// before\nreq := &structs.NodeDetachVolumeRequest{PluginID: pluginID, AllocID: allocID}\n// after\nif claim.VolumeID == \"\" { return nil }\nreq := &structs.NodeDetachVolumeRequest{PluginID: pluginID, VolumeID: claim.VolumeID, AllocID: allocID}","handlingStrategy":"validation","validationCode":"if req == nil || req.VolumeID == \"\" {\n    return errors.New(\"NodeDetachVolume requires a non-empty VolumeID\")\n}","typeGuard":"func detachNodeReady(req *structs.NodeDetachVolumeRequest) bool {\n    return req != nil && req.PluginID != \"\" && req.VolumeID != \"\" && req.AllocID != \"\"\n}","tryCatchPattern":"if err := client.NodeDetachVolume(req, &resp); err != nil {\n    if strings.Contains(err.Error(), \"VolumeID is required\") {\n        return nil // no claim to detach\n    }\n    return err\n}","preventionTips":["Derive VolumeID from the alloc's volume claims before unmount","Skip detaches for allocations with no volume claims","Test GC/unmount ordering to avoid lost volume IDs"],"tags":["csi","validation","nomad","storage"],"backgroundTag":"missing-required-argument","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}