{"record":{"id":"797195c30c3eff24","repo":"hashicorp/nomad","slug":"csi-controllerattachvolume-volumeid-is-required","errorCode":null,"errorMessage":"CSI.ControllerAttachVolume: VolumeID is required","messagePattern":"CSI\\.ControllerAttachVolume: VolumeID is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/csi_endpoint.go","lineNumber":107,"sourceCode":"// In the future this may be expanded to request dynamic secrets for attachment.\nfunc (c *CSI) ControllerAttachVolume(req *structs.ClientCSIControllerAttachVolumeRequest, resp *structs.ClientCSIControllerAttachVolumeResponse) error {\n\tdefer metrics.MeasureSince([]string{\"client\", \"csi_controller\", \"publish_volume\"}, time.Now())\n\tplugin, err := c.findControllerPlugin(req.PluginID)\n\tif err != nil {\n\t\t// the server's view of the plugin health is stale, so let it know it\n\t\t// should retry with another controller instance\n\t\treturn fmt.Errorf(\"CSI.ControllerAttachVolume: %w: %v\",\n\t\t\tnstructs.ErrCSIClientRPCRetryable, err)\n\t}\n\tdefer plugin.Close()\n\n\t// The following block of validation checks should not be reached on a\n\t// real Nomad cluster as all of this data should be validated when registering\n\t// volumes with the cluster. They serve as a defensive check before forwarding\n\t// requests to plugins, and to aid with development.\n\n\tif req.VolumeID == \"\" {\n\t\treturn errors.New(\"CSI.ControllerAttachVolume: VolumeID is required\")\n\t}\n\n\tif req.ClientCSINodeID == \"\" {\n\t\treturn errors.New(\"CSI.ControllerAttachVolume: ClientCSINodeID is required\")\n\t}\n\n\tcsiReq, err := req.ToCSIRequest()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"CSI.ControllerAttachVolume: %v\", err)\n\t}\n\n\t// Submit the request for a volume to the CSI Plugin.\n\tctx, cancelFn := c.requestContext()\n\tdefer cancelFn()\n\t// CSI ControllerPublishVolume errors for timeout, codes.Unavailable and\n\t// codes.ResourceExhausted are retried; all other errors are fatal.\n\tcresp, err := plugin.ControllerPublishVolume(ctx, csiReq,\n\t\tgrpc_retry.WithPerRetryTimeout(CSIPluginRequestTimeout),","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/csi_endpoint.go#L89-L125","documentation":"Nomad client RPC ControllerAttachVolume validates the attach request before forwarding it to the CSI controller plugin. It returns this error when the request's VolumeID field is empty. Per the source comment this is a defensive check that should not be reached on a real cluster, since volume registration normally validates this data; it mainly aids development.","triggerScenarios":"Calling the client's ControllerAttachVolume RPC (via structs.ControllerAttachVolumeRequest) with VolumeID set to \"\" — e.g. constructing the request manually in tests or from a plugin/volume reconciler that lost the volume ID.","commonSituations":"Hand-written RPC calls in dev/test harnesses; code paths that build attach requests from partially hydrated state; bugs in volume claim handling where the volume record was never resolved to an ID.","solutions":["Set req.VolumeID to the CSI volume ID from the registered volume (structs.CSIVolume) before calling ControllerAttachVolume","Fetch the volume via state store / API to get its ID instead of passing a name","If hit in production, check volume registration on the cluster — the volume may have been deregistered or GC'd between claim and attach"],"exampleFix":"// before\nreq := &structs.ControllerAttachVolumeRequest{ClientCSINodeID: nodeID, ExternalID: extID}\nerr := client.ControllerAttachVolume(req, &resp)\n// after\nreq := &structs.ControllerAttachVolumeRequest{VolumeID: vol.ID, ClientCSINodeID: nodeID, ExternalID: extID}\nif req.VolumeID == \"\" { return fmt.Errorf(\"cannot attach: volume %q has no ID\", vol.Name) }\nerr := client.ControllerAttachVolume(req, &resp)","handlingStrategy":"validation","validationCode":"if req == nil || req.VolumeID == \"\" {\n    return errors.New(\"ControllerAttachVolume requires a non-empty VolumeID\")\n}","typeGuard":"func attachable(req *structs.ControllerAttachVolumeRequest) bool {\n    return req != nil && req.VolumeID != \"\"\n}","tryCatchPattern":"if err := client.ControllerAttachVolume(req, &resp); err != nil {\n    if strings.Contains(err.Error(), \"VolumeID is required\") {\n        return fmt.Errorf(\"attach skipped, volume %q not resolved: %w\", volName, err)\n    }\n    return err\n}","preventionTips":["Always build attach requests from a hydrated structs.CSIVolume, not manual literals","Check volume registration succeeded before issuing attach","Add a precondition assertion on req.VolumeID in tests"],"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"}