hashicorp/nomad · error
node ID cannot be updated
Error message
node ID cannot be updated
What it means
HostVolume.ValidateUpdate prevents mutating a host volume's node placement after creation. If the update request sets NodeID and it differs from the existing volume's NodeID, the sentinel 'node ID cannot be updated' is appended. Immutable placement keeps allocations and scheduler bookkeeping consistent.
Source
Thrown at nomad/structs/host_volumes.go:189
}
// ValidateUpdate verifies that an update to a volume is safe to make.
func (hv *HostVolume) ValidateUpdate(existing *HostVolume) error {
if existing == nil {
return nil
}
var mErr *multierror.Error
if len(existing.Allocations) > 0 {
allocIDs := helper.ConvertSlice(existing.Allocations,
func(a *AllocListStub) string { return a.ID })
mErr = multierror.Append(mErr, fmt.Errorf(
"cannot update a volume in use: claimed by allocs (%s)",
strings.Join(allocIDs, ", ")))
}
if hv.NodeID != "" && hv.NodeID != existing.NodeID {
mErr = multierror.Append(mErr, errors.New("node ID cannot be updated"))
}
if hv.NodePool != "" && hv.NodePool != existing.NodePool {
mErr = multierror.Append(mErr, errors.New("node pool cannot be updated"))
}
if hv.RequestedCapacityMaxBytes > 0 &&
hv.RequestedCapacityMaxBytes < existing.CapacityBytes {
mErr = multierror.Append(mErr, fmt.Errorf(
"capacity_max (%d) cannot be less than existing provisioned capacity (%d)",
hv.RequestedCapacityMaxBytes, existing.CapacityBytes))
}
return mErr.ErrorOrNil()
}
const DefaultHostVolumePlugin = "default"
// CanonicalizeForCreate is called in the RPC handler to ensure we call clientView on GitHub (pinned to 482b49bf1a)
Solutions
- Remove the node_id change — keep it identical to the existing volume's NodeID
- Omit node_id from the update payload entirely (empty NodeID skips this check)
- Delete and re-create the volume on the desired node if relocation is truly needed
- Validate the diff before submission to ensure only mutable fields (e.g. capacity, capabilities) change
Example fix
// before update := existing.Copy() update.NodeID = "new-node-uuid" // mutation not allowed // after update := existing.Copy() // NodeID left unchanged; update only mutable fields update.RequestedCapacityMaxBytes = 200 * 1024 * 1024 * 1024
Defensive patterns
Strategy: validation
Validate before calling
if update.NodeID != "" && update.NodeID != existing.NodeID {
return fmt.Errorf("node_id is immutable (%s != %s)", existing.NodeID, update.NodeID)
} Prevention
- Build updates by copying existing volume and mutating only allowed fields
- Treat node_id/node_pool as read-only after creation
- Diff update payloads against current state in automation
When it happens
Trigger: Calling ValidateUpdate (via validateVolumeUpdate) on a host volume where the incoming hv.NodeID is non-empty and != existing.NodeID.
Common situations: Attempting to move a volume to a different node via the volumes update API; copy/pasting a volume definition between nodes and re-registering; automation that rewrites the whole volume spec including node_id.
Related errors
- node pool cannot be updated
- cannot register volume: node ID is required
- cannot register volume: host path is required
- cannot toggle global mode
- cannot update expiration TTL
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/f4bc6f8f5f17db9f.
Report an issue: GitHub.