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
- Omit the snapshot ID from the update spec (empty SnapshotID is ignored) and update only mutable fields such as secrets or context
- Create a new volume registration referencing the desired snapshot rather than mutating the existing one
- 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
- Omit snapshot_id from any update/deregister-then-register workflow specs
- To re-point data at a new snapshot, register a new volume rather than mutating
- Validate spec changes against `nomad volume status -verbose` output before submitting
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
- volume clone ID cannot be updated
- volume topology request update was not compatible with exist
- volume parameters cannot be updated
- volume name cannot be updated
- volume external ID cannot be updated
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/d661e15c7e92831b.
Report an issue: GitHub.