{"record":{"id":"6e44ead6a9af6f37","repo":"argoproj/argo-workflows","slug":"expected-an-array-got-v","errorCode":null,"errorMessage":"expected an array, got %v","messagePattern":"expected an array, got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/auth/types/claims.go","lineNumber":74,"sourceCode":"\tif localClaim.RawClaim[\"email_verified\"] == true || localClaim.RawClaim[\"email_verified\"] == \"true\" {\n\t\tlocalClaim.EmailVerified = true\n\t}\n\n\t*c = Claims(localClaim)\n\treturn nil\n}\n\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","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/server/auth/types/claims.go#L56-L92","documentation":"GetCustomGroup found the configured custom claim key in the RawClaim map but its value is not a JSON array. Custom SSO groups must be a list of strings; any other JSON type (string, object, number) hits this guard.","triggerScenarios":"HandleCallback reads the custom group claim and the IdP emitted it as a single string (e.g. groups: \"team-a\") or an object/map rather than an array.","commonSituations":"IdPs that send a single group as a scalar when the user has one membership; misconfigured Dex/Keycloak mappers producing a comma-joined string instead of an array.","solutions":["Configure the IdP to always emit the claim as an array (even for a single value)","Fix the claim/mapper mapping (Keycloak mapper type, Dex connector config) to produce JSON arrays","Change customGroupClaimName to a claim that is genuinely an array","Handle mixed scalar/array at ingestion if you control the code"],"exampleFix":"// before (token claim)\n{ \"groups\": \"team-a\" }\n// after (IdP mapper emits array)\n{ \"groups\": [\"team-a\"] }","handlingStrategy":"type-guard","validationCode":"v, ok := claims.RawClaim[key].([]any)\nif !ok { /* claim is not an array — fix IdP mapper or pick another claim */ }","typeGuard":"func isArrayClaim(v any) bool {\n    _, ok := v.([]any)\n    return ok\n}","tryCatchPattern":"groups, err := claims.GetCustomGroup(key)\nif err != nil {\n    return fmt.Errorf(\"group claim %q not an array: %w\", key, err)\n}","preventionTips":["Ensure the IdP emits arrays even for single membership","Verify mapper output with a decoded token after each IdP change","Prefer claims documented as multivalued","Add a payload sample check in SSO integration tests"],"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"}