argoproj/argo-workflows · error

group name %v was not a string

Error message

group name %v was not a string

What it means

GetCustomGroup was iterating the custom claim's array but one element is not a string, so it cannot become a group name. The message prints the offending element; the whole extraction aborts rather than skipping it.

Source

Thrown at server/auth/types/claims.go:81

// GetCustomGroup is responsible for extracting groups based on the
// provided custom claim key
func (c *Claims) GetCustomGroup(customKeyName string) ([]string, error) {
	groups, ok := c.RawClaim[customKeyName]
	if !ok {
		return nil, fmt.Errorf("no claim found for key: %v", customKeyName)
	}

	sliceInterface, ok := groups.([]any)
	if !ok {
		return nil, fmt.Errorf("expected an array, got %v", groups)
	}

	newSlice := []string{}
	for _, a := range sliceInterface {
		val, ok := a.(string)
		if !ok {
			return nil, fmt.Errorf("group name %v was not a string", a)
		}
		newSlice = append(newSlice, val)
	}

	return newSlice, nil
}

func (c *Claims) GetUserInfoGroups(ctx context.Context, httpClient HTTPClient, accessToken, issuer, userInfoPath string) ([]string, error) {
	url := fmt.Sprintf("%s%s", issuer, userInfoPath)
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

	if err != nil {
		return nil, err
	}

	bearer := fmt.Sprintf("Bearer %s", accessToken)
	request.Header.Set("Authorization", bearer)

View on GitHub (pinned to 35bff19146)

Solutions

  1. Configure the IdP mapper to emit plain string group names
  2. Filter/null-strip the claim on the provider side so only strings remain
  3. Pick a different claim key that contains simple string arrays
  4. If you control the code, coerce/skip non-string elements instead of erroring

Example fix

// before
{ "groups": [123, "team-a"] }
// after
{ "groups": ["123", "team-a"] }
Defensive patterns

Strategy: type-guard

Validate before calling

for _, e := range claims.RawClaim[key].([]any) {
    if _, ok := e.(string); !ok { /* non-string element: fix mapper */ }
}

Type guard

func isStringSlice(v any) ([]string, bool) {
    arr, ok := v.([]any)
    if !ok { 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

groups, err := claims.GetCustomGroup(key)
if err != nil {
    return fmt.Errorf("group claim has non-string members: %w", err)
}

Prevention

When it happens

Trigger: HandleCallback extracts groups where the claim array contains numbers, booleans, nested objects, or nulls — e.g. an IdP emitting group objects like [{"id":"g1"}] instead of plain strings.

Common situations: Keycloak/Okta mappers that emit structured group representations; null entries from partially-populated attributes; numeric group IDs.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/222cbeb047015062. Report an issue: GitHub.