hashicorp/nomad · error

volume identity cannot be updated: %s

Error message

volume identity cannot be updated: %s

What it means

CSI volumes have immutable identity fields: ExternalID (provider volume ID), PluginID, and Provider. If a registration attempts to update an existing volume but these identity fields differ from the stored volume, the store rejects the change with this error to prevent accidentally re-pointing a volume at a different provider volume.

Source

Thrown at nomad/state/state_store.go:2670

	for _, v := range volumes {
		if exists, err := s.namespaceExists(txn, v.Namespace); err != nil {
			return err
		} else if !exists {
			return fmt.Errorf("volume %s is in nonexistent namespace %s", v.ID, v.Namespace)
		}

		obj, err := txn.First(TableCSIVolumes, "id", v.Namespace, v.ID)
		if err != nil {
			return fmt.Errorf("volume existence check error: %v", err)
		}
		if obj != nil {
			// Allow some properties of a volume to be updated in place, but
			// prevent accidentally overwriting important properties.
			old := obj.(*structs.CSIVolume)
			if old.ExternalID != v.ExternalID ||
				old.PluginID != v.PluginID ||
				old.Provider != v.Provider {
				return fmt.Errorf("volume identity cannot be updated: %s", v.ID)
			}
		} else {
			v.CreateIndex = index
		}
		v.ModifyIndex = index

		// Allocations are copy on write, so we want to keep the Allocation ID
		// but we need to clear the pointer so that we don't store it when we
		// write the volume to the state store. We'll get it from the db in
		// denormalize.
		for allocID := range v.ReadAllocs {
			v.ReadAllocs[allocID] = nil
		}
		for allocID := range v.WriteAllocs {
			v.WriteAllocs[allocID] = nil
		}

		err = txn.Insert(TableCSIVolumes, v)

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Keep external_id, plugin_id, and provider unchanged in the updated spec
  2. If the provider volume truly changed, deregister (nomad volume deregister) and register a new volume under a new ID
  3. Revert the volume HCL to the original identity values before re-registering

Example fix

// before
id           = "my-volume"
external_id  = "vol-new-abc123"
// after: restore original identity or use a new volume ID
id           = "my-volume-2"
external_id  = "vol-new-abc123"
Defensive patterns

Strategy: validation

Validate before calling

old, _, err := client.CSIVolumes().Info(vol.ID, nil)
if err == nil && old != nil {
  if old.ExternalID != vol.ExternalID || old.PluginID != vol.PluginID || old.Provider != vol.Provider {
    return fmt.Errorf("identity fields of volume %s are immutable", vol.ID)
  }
}

Type guard

func identityChanged(old, cur *api.CSIVolume) bool {
  return old.ExternalID != cur.ExternalID ||
    old.PluginID != cur.PluginID ||
    old.Provider != cur.Provider
}

Try / catch

_, err := client.CSIVolumes().Register(vol, nil, nil)
if err != nil && strings.Contains(err.Error(), "identity cannot be updated") {
  // fetch current volume, diff identity fields, revert or deregister+register new ID
}

Prevention

When it happens

Trigger: CSIVolumeRegister on an existing volume ID whose HCL changed external_id, plugin_id, or provider compared to the stored volume.

Common situations: Re-running volume registration after the storage backend was recreated with a new external ID; migrating volumes to a different CSI plugin; copy-pasted specs with edited identity fields.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/df78a64686a50996. Report an issue: GitHub.