{"record":{"id":"222cbeb047015062","repo":"argoproj/argo-workflows","slug":"group-name-v-was-not-a-string","errorCode":null,"errorMessage":"group name %v was not a string","messagePattern":"group name (.+?) was not a string","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/auth/types/claims.go","lineNumber":81,"sourceCode":"\n// GetCustomGroup is responsible for extracting groups based on the\n// provided custom claim key\nfunc (c *Claims) GetCustomGroup(customKeyName string) ([]string, error) {\n\tgroups, ok := c.RawClaim[customKeyName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"no claim found for key: %v\", customKeyName)\n\t}\n\n\tsliceInterface, ok := groups.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"expected an array, got %v\", groups)\n\t}\n\n\tnewSlice := []string{}\n\tfor _, a := range sliceInterface {\n\t\tval, ok := a.(string)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"group name %v was not a string\", a)\n\t\t}\n\t\tnewSlice = append(newSlice, val)\n\t}\n\n\treturn newSlice, nil\n}\n\nfunc (c *Claims) GetUserInfoGroups(ctx context.Context, httpClient HTTPClient, accessToken, issuer, userInfoPath string) ([]string, error) {\n\turl := fmt.Sprintf(\"%s%s\", issuer, userInfoPath)\n\trequest, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbearer := fmt.Sprintf(\"Bearer %s\", accessToken)\n\trequest.Header.Set(\"Authorization\", bearer)\n","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/auth/types/claims.go#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"Keycloak/Okta mappers that emit structured group representations; null entries from partially-populated attributes; numeric group IDs.","solutions":["Configure the IdP mapper to emit plain string group names","Filter/null-strip the claim on the provider side so only strings remain","Pick a different claim key that contains simple string arrays","If you control the code, coerce/skip non-string elements instead of erroring"],"exampleFix":"// before\n{ \"groups\": [123, \"team-a\"] }\n// after\n{ \"groups\": [\"123\", \"team-a\"] }","handlingStrategy":"type-guard","validationCode":"for _, e := range claims.RawClaim[key].([]any) {\n    if _, ok := e.(string); !ok { /* non-string element: fix mapper */ }\n}","typeGuard":"func isStringSlice(v any) ([]string, bool) {\n    arr, ok := v.([]any)\n    if !ok { return nil, false }\n    out := make([]string, 0, len(arr))\n    for _, e := range arr {\n        s, ok := e.(string)\n        if !ok { return nil, false }\n        out = append(out, s)\n    }\n    return out, true\n}","tryCatchPattern":"groups, err := claims.GetCustomGroup(key)\nif err != nil {\n    return fmt.Errorf(\"group claim has non-string members: %w\", err)\n}","preventionTips":["Configure mappers to emit flat string arrays (no objects/nulls)","Strip null/empty entries provider-side","Test with users in zero, one, and many groups","Validate token claims in an integration test fixture"],"tags":["sso","claims","type-mismatch","oidc"],"backgroundTag":"claim-type-mismatch","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}