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 client

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Remove the node_id change — keep it identical to the existing volume's NodeID
  2. Omit node_id from the update payload entirely (empty NodeID skips this check)
  3. Delete and re-create the volume on the desired node if relocation is truly needed
  4. 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

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


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