hashicorp/nomad · error

volume snapshot ID cannot be updated

Error message

volume snapshot ID cannot be updated

What it means

Produced by CSIVolume.Merge (nomad/structs/csi.go:802) when an update attempts to change the snapshot ID a CSI volume was created from. The snapshot ID is immutable after registration; Merge collects this violation (with all other immutable-field violations) into a multierror returned as 'validation: ...'. This keeps the volume's provenance stable so controller validation and external storage remain consistent.

Source

Thrown at nomad/structs/csi.go:805

	var errs *multierror.Error

	if v.Name != other.Name && other.Name != "" {
		errs = multierror.Append(errs, errors.New("volume name cannot be updated"))
	}
	if v.ExternalID != other.ExternalID && other.ExternalID != "" {
		errs = multierror.Append(errs, errors.New(
			"volume external ID cannot be updated"))
	}
	if v.PluginID != other.PluginID {
		errs = multierror.Append(errs, errors.New(
			"volume plugin ID cannot be updated"))
	}
	if v.CloneID != other.CloneID && other.CloneID != "" {
		errs = multierror.Append(errs, errors.New(
			"volume clone ID cannot be updated"))
	}
	if v.SnapshotID != other.SnapshotID && other.SnapshotID != "" {
		errs = multierror.Append(errs, errors.New(
			"volume snapshot ID cannot be updated"))
	}

	// must be compatible with volume_capabilities
	if v.AccessMode != CSIVolumeAccessModeUnknown ||
		v.AttachmentMode != CSIVolumeAttachmentModeUnknown {
		var ok bool
		for _, cap := range other.RequestedCapabilities {
			if cap.AccessMode == v.AccessMode &&
				cap.AttachmentMode == v.AttachmentMode {
				ok = true
				break
			}
		}
		if ok {
			v.RequestedCapabilities = other.RequestedCapabilities
		} else {
			errs = multierror.Append(errs, errors.New(

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Omit the snapshot ID from the update spec (empty SnapshotID is ignored) and update only mutable fields such as secrets or context
  2. Create a new volume registration referencing the desired snapshot rather than mutating the existing one
  3. Deregister the existing volume first if the intent is to replace it, then register the new volume with the new snapshot ID

Example fix

// before
volume "data" {
  type        = "csi"
  snapshot_id = "snap-9999" # differs from registered value
}
// after
volume "data" {
  type = "csi"
  # snapshot_id removed; snapshot provenance is immutable
}
Defensive patterns

Strategy: validation

Validate before calling

if other.SnapshotID != "" && existing.SnapshotID != other.SnapshotID {
    return fmt.Errorf("snapshot ID %q differs from registered %q; create a new volume instead", other.SnapshotID, existing.SnapshotID)
}

Type guard

func snapshotIDMutable(existing, update *structs.CSIVolume) bool {
    return update.SnapshotID == "" || update.SnapshotID == existing.SnapshotID
}

Prevention

When it happens

Trigger: Submitting a volume update (VolumeUpsert merge path) where `other.SnapshotID` differs from the stored CSIVolume.SnapshotID and is non-empty.

Common situations: Changing the snapshot a volume references after creation to roll data back; generating specs from templates where the snapshot ID changes per run; attempting to 're-point' a volume at a newer snapshot instead of creating a new volume.

Related errors


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