hashicorp/nomad · error

expected map/object cty value, got %s

Error message

expected map/object cty value, got %s

What it means

CtyValueToMapInterface only accepts cty map or object types, since its return type is map[string]any. If the value is a list, tuple, string, number, etc., it fails with the type's FriendlyName in the message.

Source

Thrown at helper/pluginutils/hclutils/util.go:82

// CtyValueToMapInterface converts a decoded cty value into a Go
// map[string]interface{}.
//
// ParseHclInterface returns a cty.Value and callers sometimes need a generic
// map payload (for example for plugin config maps). This helper converts that
// value recursively into native Go values.
func CtyValueToMapInterface(val cty.Value) (map[string]any, error) {
	if !val.IsKnown() {
		return nil, fmt.Errorf("value is not known")
	}

	if val.IsNull() {
		return nil, nil
	}

	t := val.Type()
	if !t.IsMapType() && !t.IsObjectType() {
		return nil, fmt.Errorf("expected map/object cty value, got %s", t.FriendlyName())
	}

	v, err := ctyValueToInterface(val)
	if err != nil {
		return nil, err
	}

	m, ok := v.(map[string]any)
	if !ok {
		return nil, fmt.Errorf("expected map/object cty value, got %T", v)
	}

	return m, nil
}

func ctyValueToInterface(val cty.Value) (interface{}, error) {
	t := val.Type()

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Pass a cty map/object value; wrap non-map values in an object or change the call site to a suitable converter.
  2. Check val.Type().IsMapType() || val.Type().IsObjectType() before calling.
  3. If a list is legitimate for your use case, convert elements individually with ctyValueToInterface-style logic.

Example fix

// before
m, err := CtyValueToMapInterface(val) // val is cty.List
// after
if !val.Type().IsMapType() && !val.Type().IsObjectType() {
    return errors.New("plugin config must be a map/object")
}
m, err := CtyValueToMapInterface(val)
Defensive patterns

Strategy: type-guard

Validate before calling

t := val.Type()
if !t.IsMapType() && !t.IsObjectType() {
    return fmt.Errorf("plugin config must be a map/object, got %s", t.FriendlyName())
}

Type guard

func isMapOrObject(v cty.Value) bool {
    t := v.Type()
    return t.IsMapType() || t.IsObjectType()
}

Try / catch

m, err := CtyValueToMapInterface(val)
if err != nil {
    return fmt.Errorf("invalid plugin config: %w", err)
}

Prevention

When it happens

Trigger: Calling CtyValueToMapInterface with a cty.Value that is not a map/object — e.g. a list of strings, a primitive, or a tuple from ParseHclInterface output.

Common situations: Plugin config validation where the config value turned out to be a list or scalar instead of a map; passing the wrong variable to validatePluginConfig.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/ec57526f7a1005b9. Report an issue: GitHub.