{"record":{"id":"ce5050d990b81f31","repo":"hashicorp/nomad","slug":"missing-nodeid-ce5050","errorCode":null,"errorMessage":"missing NodeID","messagePattern":"missing NodeID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/csi/plugin.go","lineNumber":417,"sourceCode":"\t\treturn nil\n\t}\n\n\treturn &csipbv1.ControllerPublishVolumeRequest{\n\t\tVolumeId:         r.ExternalID,\n\t\tNodeId:           r.NodeID,\n\t\tReadonly:         r.ReadOnly,\n\t\tVolumeCapability: r.VolumeCapability.ToCSIRepresentation(),\n\t\tSecrets:          r.Secrets,\n\t\tVolumeContext:    r.VolumeContext,\n\t}\n}\n\nfunc (r *ControllerPublishVolumeRequest) Validate() error {\n\tif r.ExternalID == \"\" {\n\t\treturn errors.New(\"missing volume ID\")\n\t}\n\tif r.NodeID == \"\" {\n\t\treturn errors.New(\"missing NodeID\")\n\t}\n\treturn nil\n}\n\ntype ControllerPublishVolumeResponse struct {\n\tPublishContext map[string]string\n}\n\ntype ControllerUnpublishVolumeRequest struct {\n\tExternalID string\n\tNodeID     string\n\tSecrets    structs.CSISecrets\n}\n\nfunc (r *ControllerUnpublishVolumeRequest) ToCSIRepresentation() *csipbv1.ControllerUnpublishVolumeRequest {\n\tif r == nil {\n\t\treturn nil\n\t}","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/plugins/csi/plugin.go#L399-L435","documentation":"ControllerPublishVolumeRequest.Validate() rejects requests with an empty NodeID, the node identity the plugin needs to attach the volume to. The provider's attach API requires a target node; an empty NodeID is ambiguous and rejected.","triggerScenarios":"Calling ControllerPublishVolume with NodeID empty, e.g. the node was never registered with the provider or the caller passed a zero-value request.","commonSituations":"Node fingerprinting/registration not completed so the provider node ID was never recorded; using the node's hostname instead of the provider-assigned node ID; race where publish starts before NodeRegister finishes.","solutions":["Set NodeID to the provider node ID returned by NodeGetInfo (or the plugin's registered node identifier)","Ensure the node has completed registration/fingerprinting with the plugin before publishing volumes to it","Check that the caller isn't confusing the CSI node name with the provider instance ID"],"exampleFix":"// before\nreq := &csi.ControllerPublishVolumeRequest{\n    ExternalID: volumeExternalID,\n    NodeID:     \"\",\n}\n// after\nnodeInfo, err := nodeClient.NodeGetInfo(ctx, &csi.NodeGetInfoRequest{})\n// after\nreq := &csi.ControllerPublishVolumeRequest{\n    ExternalID: volumeExternalID,\n    NodeID:     nodeInfo.NodeID,\n}","handlingStrategy":"validation","validationCode":"func validateNodeID(req *csi.ControllerPublishVolumeRequest) error {\n    if req.NodeID == \"\" {\n        return errors.New(\"NodeID must be the provider node ID from NodeGetInfo\")\n    }\n    return nil\n}","typeGuard":"func hasNodeID(req *csi.ControllerPublishVolumeRequest) bool {\n    return req != nil && req.NodeID != \"\"\n}","tryCatchPattern":"if err := req.Validate(); err != nil {\n    if strings.Contains(err.Error(), \"missing NodeID\") {\n        return fmt.Errorf(\"controller publish refused: fetch NodeID via NodeGetInfo after node registration: %w\", err)\n    }\n    return err\n}","preventionTips":["Gate volume publishing on completed node registration/fingerprinting","Store the provider node ID (not hostname) at NodeGetInfo time","Fail fast if reconciliation reaches publish before the node ID is known"],"tags":["csi","validation","go","storage"],"backgroundTag":"csi-request-validation-failed","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"}