{"record":{"id":"ddaca8c73b325be8","repo":"sipeed/picoclaw","slug":"model-name-must-be-a-string-got-t","errorCode":null,"errorMessage":"model_name must be a string, got %T","messagePattern":"model_name must be a string, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/migration.go","lineNumber":506,"sourceCode":"\tdelete(m2, \"model_list\")\n\n\tm := mergeMap(m1, m2)\n\treturn m, nil\n}\n\n// mergeModelListsWithMap merges array-style model_list with map-style security model_list.\n// It generates indexed keys from model_name (like toNameIndex) and uses them\n// to look up security entries, falling back to ModelName if the indexed key doesn't exist.\nfunc mergeModelListsWithMap(mainML []any, secML map[string]any) error {\n\t// Build indexed keys like toNameIndex does\n\tindexedKeys := make(map[string]int)\n\tcountMap := make(map[string]int)\n\tfor i, m := range mainML {\n\t\tif mVal, ok := m.(map[string]any); ok {\n\t\t\tif name, hasName := mVal[\"model_name\"]; hasName {\n\t\t\t\tnameStr, ok := name.(string)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn fmt.Errorf(\"model_name must be a string, got %T\", name)\n\t\t\t\t}\n\t\t\t\tindex := countMap[nameStr]\n\t\t\t\tindexedKeys[fmt.Sprintf(\"%s:%d\", nameStr, index)] = i\n\t\t\t\tif _, ok := indexedKeys[nameStr]; !ok {\n\t\t\t\t\tindexedKeys[nameStr] = i\n\t\t\t\t}\n\t\t\t\tcountMap[nameStr]++\n\t\t\t} else {\n\t\t\t\treturn fmt.Errorf(\"model_name is required: %#v\", mVal)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor k, v := range secML {\n\t\tif i, ok := indexedKeys[k]; ok {\n\t\t\tif vv, ok := v.(map[string]any); ok {\n\t\t\t\tif mVal, ok := mainML[i].(map[string]any); ok {\n\t\t\t\t\tmVal[\"api_keys\"] = vv[\"api_keys\"]","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/config/migration.go#L488-L524","documentation":"Thrown by mergeModelListsWithMap while merging an array-style model_list with a map-style security model_list during config migration. Each array entry must carry a string model_name because it is used to build the indexed lookup keys (\"name\" and \"name:N\") that pair security entries with models. If model_name exists but is not a string after YAML decoding (Go type float64, bool, []any, map[string]any), the merge aborts with the actual Go type in the message.","triggerScenarios":"A model_list entry in YAML like `- model_name: 123`, `- model_name: true`, or `- model_name: [gpt-4]`. YAML scalars decode to non-string Go types, the type assertion `name.(string)` fails, and the error names the offending type (e.g. \"got float64\").","commonSituations":"Unquoted numeric model names (`model_name: 7b` decodes as float64), copy-pasted YAML where booleans or inline arrays land on the model_name line, or hand-edited security.yml migration input. Typically surfaces right after upgrading/migrating configs that merge main config with security config.","solutions":["Quote the value in YAML: `- model_name: \"123\"` so it decodes as a string","Find the offending entry: the message includes the Go type (float64/bool/map[string]any) which tells you what shape the value has; grep model_list for numeric/boolean/complex model_name values","If the entry was never meant to have model_name at that level, restructure it so model_name is a plain string scalar at the entry top level","Re-run the migration/config load after fixing"],"exampleFix":"# before\nmodel_list:\n  - model_name: 7b\n    provider: openai\n\n# after\nmodel_list:\n  - model_name: \"7b\"\n    provider: openai","handlingStrategy":"validation","validationCode":"// Pre-validate array-style model_list before triggering the migration/merge.\nfunc validateModelListTypes(ml []any) error {\n\tfor i, m := range ml {\n\t\tentry, ok := m.(map[string]any)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"model_list[%d]: entry is not a map\", i)\n\t\t}\n\t\tname, has := entry[\"model_name\"]\n\t\tif !has {\n\t\t\treturn fmt.Errorf(\"model_list[%d]: model_name is required\", i)\n\t\t}\n\t\tif _, ok := name.(string); !ok {\n\t\t\treturn fmt.Errorf(\"model_list[%d]: model_name must be a string, got %T\", i, name)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func isStringModelName(m any) bool {\n\tentry, ok := m.(map[string]any)\n\tif !ok { return false }\n\tname, has := entry[\"model_name\"]\n\tif !has { return false }\n\t_, isStr := name.(string)\n\treturn isStr\n}","tryCatchPattern":"if err := mergeModelListsWithMap(mainML, secML); err != nil {\n\t// Error text already includes the Go type (%T) or the entry dump (%#v).\n\treturn fmt.Errorf(\"config migration: fix model_list: %w\", err)\n}","preventionTips":["Quote numeric-looking model names in YAML (\"7b\", \"123\") so they decode as strings","Lint model_list entries in CI with a schema check before migration runs","Never nest lists/maps under model_name"],"tags":["go","config","yaml","validation","migration"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}