{"record":{"id":"25afe621e6c1e408","repo":"ipfs/kubo","slug":"s-key-is-not-a-map-25afe6","errorCode":null,"errorMessage":"%s key is not a map","messagePattern":"(.+?) key is not a map","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"repo/common/common.go","lineNumber":20,"sourceCode":"\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"strings\"\n)\n\nfunc MapGetKV(v map[string]any, key string) (any, error) {\n\tvar ok bool\n\tvar mcursor map[string]any\n\tvar cursor any = v\n\n\tparts := strings.Split(key, \".\")\n\tfor i, part := range parts {\n\t\tsofar := strings.Join(parts[:i], \".\")\n\n\t\tmcursor, ok = cursor.(map[string]any)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"%s key is not a map\", sofar)\n\t\t}\n\n\t\tcursor, ok = mcursor[part]\n\t\tif !ok {\n\t\t\t// Construct the current path traversed to print a nice error message\n\t\t\tvar path string\n\t\t\tif len(sofar) > 0 {\n\t\t\t\tpath += sofar + \".\"\n\t\t\t}\n\t\t\tpath += part\n\t\t\treturn nil, fmt.Errorf(\"%s not found\", path)\n\t\t}\n\t}\n\treturn cursor, nil\n}\n\nfunc MapSetKV(v map[string]any, key string, value any) error {\n\tvar ok bool","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/repo/common/common.go#L2-L38","documentation":"MapGetKV walks a dotted config key (e.g. \"Addresses.API\") through nested map[string]any values. At each segment it type-asserts the current level to map[string]any; if the value reached so far (path prefix \"sofar\") is not a map, it returns this error naming the non-map prefix. It surfaces from GetConfigKey/SetConfigKey when an intermediate config value is a scalar, not an object.","triggerScenarios":"Calling GetConfigKey(map, \"Addresses.API.Listener\") when config[\"Addresses\"][\"API\"] is a string (like \"/ip4/.../tcp/5001\"), so descending into \"Listener\" hits a non-map. Same for any dotted key where a mid-path value is a string, number, bool, or nil.","commonSituations":"ipfs config <scalarParent>.<child> CLI/RPC calls; scripts assuming nested structure where the config actually stores a scalar (Addresses.API is a string, Datastore.Spec is a map — mixing them up); stale config written by an older version with a different shape.","solutions":["Print the intermediate value (ipfs config <sofar>) and confirm whether it is a scalar — address it directly instead of indexing into it","Use the correct full key for scalars: e.g. get \"Addresses.API\" itself, not \"Addresses.API.child\"","Validate the config schema before traversal (check each prefix is map[string]any) and give the user a targeted message","Migrate/rewrite config entries whose shape changed across versions"],"exampleFix":"// before\nv, err := MapGetKV(cfg, \"Addresses.API.Gateway\") // \"Addresses key is not a map\"-style failure\n// after\nv, err := MapGetKV(cfg, \"Addresses.API\") // API is a string scalar; read it directly","handlingStrategy":"validation","validationCode":"func isMapPath(v map[string]any, dotted string) bool {\n    cur := any(v)\n    for _, part := range strings.Split(dotted, \".\") {\n        m, ok := cur.(map[string]any)\n        if !ok {\n            return false\n        }\n        cur, ok = m[part]\n        if !ok {\n            return true // absent is fine; not-a-map is not\n        }\n    }\n    return true\n}","typeGuard":"func asConfigMap(v any) (map[string]any, bool) {\n    m, ok := v.(map[string]any)\n    return m, ok\n}","tryCatchPattern":"v, err := GetConfigKey(cfg, \"Addresses.API.Listener\")\nif err != nil {\n    if strings.HasSuffix(err.Error(), \"key is not a map\") {\n        // fall back to reading the scalar parent\n        return GetConfigKey(cfg, \"Addresses.API\")\n    }\n    return err\n}","preventionTips":["Check docs/config.md for which sections are maps vs scalars before using dotted keys","Run ipfs config <prefix> to inspect intermediate values before indexing deeper","Guard scalar parents (Addresses.API, Addresses.Gateway) against child-key access in scripts","Validate config shape after version upgrades that change section types"],"tags":["config","golang","map-traversal"],"backgroundTag":"config-key-not-a-map","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}