{"record":{"id":"fd8182905e515ea1","repo":"kubernetes/kops","slug":"cannot-unset-field-q-marked-immutable","errorCode":null,"errorMessage":"cannot unset field %q (marked immutable)","messagePattern":"cannot unset field %q \\(marked immutable\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/pkg/reflectutils/access.go","lineNumber":323,"sourceCode":"\nfunc Unset(target interface{}, targetPath string) error {\n\ttargetValue := reflect.ValueOf(target)\n\n\ttargetFieldPath, err := ParseFieldPath(targetPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot parse field path %q: %w\", targetPath, err)\n\t}\n\n\tfieldUnset := false\n\n\tvisitor := func(path *FieldPath, field *reflect.StructField, v reflect.Value) error {\n\t\tif !targetFieldPath.HasPrefixMatch(path) {\n\t\t\treturn nil\n\t\t}\n\n\t\tif targetFieldPath.Matches(path) {\n\t\t\tif !v.CanSet() {\n\t\t\t\treturn fmt.Errorf(\"cannot unset field %q (marked immutable)\", path)\n\t\t\t}\n\n\t\t\tv.Set(reflect.Zero(v.Type()))\n\t\t\tfieldUnset = true\n\t\t\treturn nil\n\t\t}\n\n\t\treturn nil\n\t}\n\n\terr = ReflectRecursive(targetValue, visitor, &ReflectOptions{JSONNames: true})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !fieldUnset {\n\t\treturn fmt.Errorf(\"field %s not found in %s\", targetPath, BuildTypeName(reflect.TypeOf(target)))\n\t}","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/util/pkg/reflectutils/access.go#L305-L341","documentation":"During Unset, when the visitor reaches the exact field, it checks reflect Value.CanSet(). If the field is not settable — e.g. obtained through an unexported field, a nil-pointer dereference path, or a value (non-pointer) copy — the field is effectively immutable and this error is returned rather than silently skipping the zeroing.","triggerScenarios":"Calling kops unset on a field that the reflection walk cannot address for writing, e.g. unsetting a field inside a struct reached through an unexported field or an interface/value-typed portion of the spec.","commonSituations":"Attempting to unset fields inside embedded read-only structures or on objects passed by value; kops API types are normally pointer-addressable, so this usually signals a deeply unusual path.","solutions":["Ensure the target object is passed as a pointer (*api.Cluster / *api.InstanceGroup)","Choose a different field path that traverses only exported, pointer-addressable fields","Fall back to editing the YAML manifest directly and `kops replace -f`"],"exampleFix":"// before\nvar c api.Cluster\nUnset(&wrapper, \"spec.kubelet\") // unreachable inner field\n// after\nc := api.Cluster{}\nUnset(&c, \"spec.kubelet.cpuCFSQuota\") // pointer to top-level object","handlingStrategy":"validation","validationCode":"if reflect.ValueOf(target).Kind() != reflect.Ptr || reflect.ValueOf(target).IsNil() {\n\treturn fmt.Errorf(\"target must be a non-nil pointer\")\n}","typeGuard":"func isSettablePointer(target interface{}) bool {\n\tv := reflect.ValueOf(target)\n\treturn v.Kind() == reflect.Ptr && !v.IsNil() && v.Elem().CanSet()\n}","tryCatchPattern":"if err := Unset(target, path); err != nil && strings.Contains(err.Error(), \"marked immutable\") {\n\t// edit the manifest and `kops replace -f` instead\n}","preventionTips":["Always pass *api.Cluster / *api.InstanceGroup pointers to Unset","Avoid paths traversing unexported or value-only fields","For read-only fields, modify the YAML manifest directly"],"tags":["reflection","immutability","go"],"backgroundTag":"field-not-settable","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T07:17:12.445Z"}