hashicorp/nomad · error

%q list claim could not be converted to string list

Error message

%q list claim could not be converted to string list

What it means

extractListMappings expects each ListClaimMappings source claim to be a list (or a scalar that can be wrapped into a one-element list). If the raw value is a map or other non-convertible type, normalizeList fails and this error is returned.

Source

Thrown at lib/auth/claims.go:108

//
//	{
//	    "/some/claim/pointer": "metadata_key1",
//	    "another_claim": "metadata_key2",
//	     ...
//	}
func extractListMappings(
	all map[string]interface{}, mappings map[string]string) (map[string][]string, error) {

	result := make(map[string][]string)
	for source, target := range mappings {
		rawValue := getClaim(all, source)
		if rawValue == nil {
			continue
		}

		rawList, ok := normalizeList(rawValue)
		if !ok {
			return nil, fmt.Errorf("%q list claim could not be converted to string list", source)
		}

		list := make([]string, 0, len(rawList))
		for _, raw := range rawList {
			value, ok := stringifyClaimValue(raw)
			if !ok {
				return nil, fmt.Errorf("value %v in %q list claim could not be parsed as string",
					raw, source)
			}

			if value == "" {
				continue
			}
			list = append(list, value)
		}

		result[target] = list
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Point the list claim mapping at the actual array, e.g. /resource_access/myclient/roles
  2. Decode the JWT and confirm the claim's shape before configuring mappings
  3. Use ClaimMappings (string) instead if the value is a single scalar

Example fix

// before
ListClaimMappings: {"/resource_access": "roles"}
// after
ListClaimMappings: {"/resource_access/api/roles": "roles"}
Defensive patterns

Strategy: type-guard

Validate before calling

func isListShapedClaim(v interface{}) bool {
    switch v.(type) {
    case []interface{}, string, bool, float64:
        return true
    default:
        return false // map[string]interface{} and similar fail
    }
}

Type guard

func asStringList(v interface{}) ([]string, bool) {
    arr, ok := v.([]interface{})
    if !ok {
        if s, ok := v.(string); ok {
            return []string{s}, true
        }
        return nil, false
    }
    out := make([]string, 0, len(arr))
    for _, e := range arr {
        s, ok := e.(string)
        if !ok {
            return nil, false
        }
        out = append(out, s)
    }
    return out, true
}

Try / catch

if err != nil && strings.Contains(err.Error(), "could not be converted to string list") {
    return fmt.Errorf("list claim mapping targets a non-list claim: %w", err)
}

Prevention

When it happens

Trigger: Login where a ListClaimMappings claim resolves to map[string]interface{} (an object) — e.g. mapping "/resource_access" (object of objects) directly as a list claim.

Common situations: Keycloak resource_access objects misconfigured as list claims; IdP emits a single-element list collapsed into a scalar of an unexpected type; claim schema changed on the provider side.

Related errors


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