hashicorp/nomad · error

missing ExternalVolumeID

Error message

missing ExternalVolumeID

What it means

This error is returned by ControllerDeleteVolumeRequest.Validate when the ExternalVolumeID field is empty. Deleting a volume on a CSI storage provider requires the provider-assigned external volume ID, so an empty ID indicates a malformed request and is rejected locally before any gRPC call is made.

Source

Thrown at plugins/csi/plugin.go:633

type ControllerDeleteVolumeRequest struct {
	ExternalVolumeID string
	Secrets          structs.CSISecrets
}

func (r *ControllerDeleteVolumeRequest) ToCSIRepresentation() *csipbv1.DeleteVolumeRequest {
	if r == nil {
		return nil
	}
	return &csipbv1.DeleteVolumeRequest{
		VolumeId: r.ExternalVolumeID,
		Secrets:  r.Secrets,
	}
}

func (r *ControllerDeleteVolumeRequest) Validate() error {
	if r.ExternalVolumeID == "" {
		return errors.New("missing ExternalVolumeID")
	}
	return nil
}

type ControllerExpandVolumeRequest struct {
	ExternalVolumeID string
	RequiredBytes    int64
	LimitBytes       int64
	Capability       *VolumeCapability
	Secrets          structs.CSISecrets
}

func (r *ControllerExpandVolumeRequest) Validate() error {
	if r.ExternalVolumeID == "" {
		return errors.New("missing ExternalVolumeID")
	}
	if r.LimitBytes == 0 && r.RequiredBytes == 0 {
		return errors.New("one of LimitBytes or RequiredBytes must be set")

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Populate ExternalVolumeID with the provider-assigned volume ID from the create/publish response before deleting.
  2. Inspect the CSI volume in Nomad (nomad volume status) to find the correct external ID.
  3. Fix the code path that produced an empty ID — check for failed CreateVolume responses that were not propagated.
  4. Add a pre-call check that skips deletion when the external ID is empty/unset.

Example fix

// before
req := &ControllerDeleteVolumeRequest{} // ExternalVolumeID empty
// after
req := &ControllerDeleteVolumeRequest{
  ExternalVolumeID: vol.RemoteID(), // ID from ControllerCreateVolumeResponse
}
Defensive patterns

Strategy: validation

Validate before calling

if req.ExternalVolumeID == "" {
	return fmt.Errorf("cannot delete volume: no external ID recorded")
}

Type guard

func hasExternalVolumeID(r *ControllerDeleteVolumeRequest) bool {
	return r != nil && r.ExternalVolumeID != ""
}

Prevention

When it happens

Trigger: Invoking the controller delete-volume path (via the plugin client) with ControllerDeleteVolumeRequest{ExternalVolumeID: ""} — typically when the volume record never received an ID from a prior ControllerPublishVolume/CreateVolume response, or the request was constructed with a zero-value struct.

Common situations: Deregistering a Nomad CSI volume whose controller publish step failed so no external ID was stored; passing an empty variable interpolated into the ID field; client code building the request struct manually without setting ExternalVolumeID.

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