kubernetes/kops · error

cannot set field %q (marked immutable)

Error message

cannot set field %q (marked immutable)

What it means

SetString's visitor rejects setting a field when reflect reports !v.CanSet() — the value is not addressable or reached through an unexported field — and the message calls it 'marked immutable'. The library cannot write through the reflection value at that path.

Source

Thrown at util/pkg/reflectutils/access.go:48

func SetString(target interface{}, targetPath string, newValue string) error {
	targetValue := reflect.ValueOf(target)

	targetFieldPath, err := ParseFieldPath(targetPath)
	if err != nil {
		return fmt.Errorf("cannot parse field path %q: %w", targetPath, err)
	}

	fieldSet := false

	visitor := func(path *FieldPath, field *reflect.StructField, v reflect.Value) error {
		if !targetFieldPath.HasPrefixMatch(path) {
			return nil
		}

		if targetFieldPath.Matches(path) {
			if !v.CanSet() {
				return fmt.Errorf("cannot set field %q (marked immutable)", path)
			}

			if err := setType(v, newValue); err != nil {
				return fmt.Errorf("cannot set field %q: %v", path, err)
			}

			fieldSet = true
			return nil
		}

		// Partial match, append the next explicitly indexed slice element.
		if v.Kind() == reflect.Slice {
			if len(targetFieldPath.elements) > len(path.elements) {
				next := targetFieldPath.elements[len(path.elements)]
				if next.Type == FieldPathElementTypeArrayIndex && next.number == v.Len() {
					if !v.CanSet() {
						return fmt.Errorf("cannot set field %q (marked immutable)", path)
					}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Pass a pointer to the target struct (&cluster) so reflection values are addressable
  2. Choose a different, settable field path — some spec fields are intentionally immutable after creation
  3. Recreate the resource with the desired value instead of mutating the existing one

Example fix

// before
reflectutils.SetString(*cluster, "spec.kubernetesVersion", "v1.28.0") // copy, not settable
// after
reflectutils.SetString(cluster, "spec.kubernetesVersion", "v1.28.0") // cluster is *api.Cluster
Defensive patterns

Strategy: validation

Validate before calling

func settable(target interface{}, path string) error {
	v := reflect.ValueOf(target)
	if v.Kind() != reflect.Ptr || v.IsNil() {
		return fmt.Errorf("target must be a non-nil pointer")
	}
	return nil
}

Type guard

func isSettableTarget(t interface{}) bool {
	v := reflect.ValueOf(t)
	return v.Kind() == reflect.Ptr && !v.IsNil() && v.Elem().CanSet()
}

Try / catch

if err := reflectutils.SetString(target, path, val); err != nil {
	if strings.Contains(err.Error(), "marked immutable") {
		return fmt.Errorf("field %s cannot be modified; recreate the resource instead", path)
	}
	return err
}

Prevention

When it happens

Trigger: Calling SetString targeting a field inside a non-pointer struct, an unexported struct field, or a value obtained by value (not pointer) — e.g. SetString(clusterValue, ...) where clusterValue is a struct copy rather than &cluster.

Common situations: kops set / kops replace on immutable cluster fields; trying to modify fields of a struct passed by value; walking into unexported internals of the kops API types.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/5a98d2cb37ef5d14. Report an issue: GitHub.