docker/cli · critical

expected a map or a list, got %T: %#v

Error message

expected a map or a list, got %T: %#v

What it means

This is a PANIC, not a returned error. transformListOrMapping (used by hosts/extra_hosts/dns list forms) only handles `map[string]any` and `[]any`; any other type calls panic(). Because Load() does not recover, the panic crashes the process. Callers must ensure the field is a map or list before it reaches the transformer.

Solutions

  1. Provide a list or mapping for the field, e.g. `extra_hosts: ["host1:1.2.3.4"]`.
  2. If calling the loader programmatically, wrap Load()/Transform() in a recover() to convert the panic into an error.

Example fix

# before
web:
  extra_hosts: host1
# after
web:
  extra_hosts:
    - "host1:10.0.0.1"
Defensive patterns

Strategy: try-catch

Validate before calling

// transformListOrMapping panics on non-map/non-list; pre-check the field.
func validateListOrMapping(field string, v any) error {
    switch v.(type) {
    case map[string]any, []any:
        return nil
    default:
        return fmt.Errorf("%s must be a mapping or list, got %T", field, v)
    }
}

Type guard

func isMapOrList(v any) bool {
    switch v.(type) {
    case map[string]any, []any:
        return true
    }
    return false
}

Try / catch

// Load() does not recover; wrap it when calling programmatically.
func safeLoad(details types.ConfigDetails) (cfg *types.Config, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("compose load panic: %v", r)
        }
    }()
    return Load(details)
}

Prevention

When it happens

Trigger: A field routed through transformListOrMapping receives a scalar (e.g. `extra_hosts: host1` as a bare string) or null. The function has no default branch — it panics.

Common situations: Mis-typing a list field as a scalar; templating emitting null; downstream code calling the transformer directly with a non-conforming value.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/2f2f02f35d182ba3. Report an issue: GitHub.

Appendix: source

Thrown at cli/compose/loader/loader.go:847

			for i, allowSep := range allowSeps {
				entry := fmt.Sprint(entry)
				k, v, ok := strings.Cut(entry, allowSep)
				if ok {
					// Entry uses this allowed separator. Add it to the result, using
					// sep as a separator.
					result = append(result, fmt.Sprintf("%s%s%s", k, sep, v))
					break
				} else if i == len(allowSeps)-1 {
					// No more separators to try, keep the entry if allowNil.
					if allowNil {
						result = append(result, k)
					}
				}
			}
		}
		return result
	}
	panic(fmt.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping))
}

func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
	return func(data any) (any, error) {
		return transformMappingOrList(data, sep, allowNil), nil
	}
}

func transformMappingOrList(mappingOrList any, sep string, allowNil bool) any {
	switch values := mappingOrList.(type) {
	case map[string]any:
		return toMapStringString(values, allowNil)
	case []any:
		result := make(map[string]any)
		for _, v := range values {
			key, val, hasValue := strings.Cut(v.(string), sep)
			switch {
			case !hasValue && allowNil:

View on GitHub (pinned to 4f84911bfe)