ipfs/kubo · error

%s key is not a map

Error message

%s key is not a map

What it means

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.

Source

Thrown at repo/common/common.go:20

import (
	"fmt"
	"maps"
	"strings"
)

func MapGetKV(v map[string]any, key string) (any, error) {
	var ok bool
	var mcursor map[string]any
	var cursor any = v

	parts := strings.Split(key, ".")
	for i, part := range parts {
		sofar := strings.Join(parts[:i], ".")

		mcursor, ok = cursor.(map[string]any)
		if !ok {
			return nil, fmt.Errorf("%s key is not a map", sofar)
		}

		cursor, ok = mcursor[part]
		if !ok {
			// Construct the current path traversed to print a nice error message
			var path string
			if len(sofar) > 0 {
				path += sofar + "."
			}
			path += part
			return nil, fmt.Errorf("%s not found", path)
		}
	}
	return cursor, nil
}

func MapSetKV(v map[string]any, key string, value any) error {
	var ok bool

View on GitHub (pinned to 329838acdf)

Solutions

  1. Print the intermediate value (ipfs config <sofar>) and confirm whether it is a scalar — address it directly instead of indexing into it
  2. Use the correct full key for scalars: e.g. get "Addresses.API" itself, not "Addresses.API.child"
  3. Validate the config schema before traversal (check each prefix is map[string]any) and give the user a targeted message
  4. Migrate/rewrite config entries whose shape changed across versions

Example fix

// before
v, err := MapGetKV(cfg, "Addresses.API.Gateway") // "Addresses key is not a map"-style failure
// after
v, err := MapGetKV(cfg, "Addresses.API") // API is a string scalar; read it directly
Defensive patterns

Strategy: validation

Validate before calling

func isMapPath(v map[string]any, dotted string) bool {
    cur := any(v)
    for _, part := range strings.Split(dotted, ".") {
        m, ok := cur.(map[string]any)
        if !ok {
            return false
        }
        cur, ok = m[part]
        if !ok {
            return true // absent is fine; not-a-map is not
        }
    }
    return true
}

Type guard

func asConfigMap(v any) (map[string]any, bool) {
    m, ok := v.(map[string]any)
    return m, ok
}

Try / catch

v, err := GetConfigKey(cfg, "Addresses.API.Listener")
if err != nil {
    if strings.HasSuffix(err.Error(), "key is not a map") {
        // fall back to reading the scalar parent
        return GetConfigKey(cfg, "Addresses.API")
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ipfs/kubo@329838acdf (2026-09-03). Data as JSON: /api/errors/25afe621e6c1e408. Report an issue: GitHub.