{"record":{"id":"fa9b82ecc010c77e","repo":"semaphoreui/semaphore","slug":"expected-slice-for-field-s-but-got-t","errorCode":null,"errorMessage":"expected slice for field %s but got %T","messagePattern":"expected slice for field (.+?) but got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/config.go","lineNumber":1198,"sourceCode":"\t\t\t\t\tvar sourceSlice reflect.Value\n\t\t\t\t\tif val.Kind() == reflect.Slice || val.Kind() == reflect.Array {\n\t\t\t\t\t\tsourceSlice = val\n\t\t\t\t\t} else if val.Kind() == reflect.String {\n\t\t\t\t\t\t// Try to parse JSON array from string\n\t\t\t\t\t\tstr := val.String()\n\t\t\t\t\t\t// First, try to unmarshal into []any\n\t\t\t\t\t\tvar anyArr []any\n\t\t\t\t\t\tif err := json.Unmarshal([]byte(str), &anyArr); err == nil {\n\t\t\t\t\t\t\tsourceSlice = reflect.ValueOf(anyArr)\n\t\t\t\t\t\t} else if fieldElemType.Kind() == reflect.String {\n\t\t\t\t\t\t\t// Fallback: treat as single element string\n\t\t\t\t\t\t\tsourceSlice = reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(\"\")), 1, 1)\n\t\t\t\t\t\t\tsourceSlice.Index(0).SetString(str)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn fmt.Errorf(\"expected slice or json array string for field %s but got %T\", field.Name, value)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected slice for field %s but got %T\", field.Name, value)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Build destination slice\n\t\t\t\t\tnewSlice := reflect.MakeSlice(fieldValue.Type(), 0, sourceSlice.Len())\n\t\t\t\t\tfor i := 0; i < sourceSlice.Len(); i++ {\n\t\t\t\t\t\tsrcElemVal := sourceSlice.Index(i)\n\t\t\t\t\t\t// When source is []any, elements come as interface{}, unwrap reflect.Value\n\t\t\t\t\t\tif srcElemVal.Kind() == reflect.Interface && !srcElemVal.IsNil() {\n\t\t\t\t\t\t\tsrcElemVal = reflect.ValueOf(srcElemVal.Interface())\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar dstElem reflect.Value\n\t\t\t\t\t\t// Prepare destination element\n\t\t\t\t\t\tif fieldElemType.Kind() == reflect.Struct {\n\t\t\t\t\t\t\tdstElem = reflect.New(fieldElemType).Elem()\n\t\t\t\t\t\t\tif srcElemVal.Kind() == reflect.Map {\n\t\t\t\t\t\t\t\t// Expect map[string]any\n\t\t\t\t\t\t\t\tmIface, ok := srcElemVal.Interface().(map[string]any)","sourceCodeStart":1180,"sourceCodeEnd":1216,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/config.go#L1180-L1216","documentation":"When a struct field is a slice, the source value must be a Go slice/array or a string parseable as a slice (JSON array, or single string for []string fields). Any other kind (int, bool, float, nil-typed value, etc.) returns this error. It reports that a non-slice value was supplied for a slice field.","triggerScenarios":"Calling AssignMapToStruct with m[\"field\"] = 42 or true while the target struct field is a slice type (e.g. []string, []int).","commonSituations":"Config schema drift: a field changed from scalar to slice (or vice versa) and old config maps still hold a scalar; hand-built maps where the wrong key was set; merged config where a value was overwritten by a scalar default.","solutions":["Wrap the scalar in a slice in the source map: []any{value} or []string{value}.","Check whether the struct field type is correct — if the value is genuinely scalar, change the field or the key.","Validate the map values against the struct shape (reflect over structType fields) before assignment.","If the value comes from env/file config, pre-parse it into the proper slice type."],"exampleFix":"// before\nm := map[string]any{\"hosts\": \"web-1\"} // field is []string? no — scalar in map for slice field of non-string elem\n// after\nm := map[string]any{\"hosts\": []any{\"web-1\"}}","handlingStrategy":"validation","validationCode":"func expectSlice(m map[string]any, key string) error {\n  v, ok := m[key]\n  if !ok { return nil }\n  switch v.(type) {\n  case []any, []string, []int, string:\n    return nil\n  default:\n    return fmt.Errorf(\"%s must be a slice or JSON array string, got %T\", key, v)\n  }\n}","typeGuard":"func isSliceLike(v any) bool {\n  switch v.(type) {\n  case []any, []string, []int, []map[string]any:\n    return true\n  }\n  rv := reflect.ValueOf(v)\n  return rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array\n}","tryCatchPattern":"if err := util.AssignMapToStruct(m, &cfg); err != nil {\n  if strings.Contains(err.Error(), \"expected slice for field\") {\n    log.Fatalf(\"config value must be a list: %v\", err)\n  }\n}","preventionTips":["Keep config map values shape-consistent with the struct across versions.","When promoting a scalar field to a slice, wrap legacy values in []any at the config loader.","Validate map keys/types against the struct before calling AssignMapToStruct."],"tags":["reflection","config","go","slice"],"backgroundTag":"config-type-mismatch","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}