GET /groups/:id/service_accounts/:user_id/personal_access_tokens returns 500 ArgumentError when service account has admin privileges
#### Summary
The `GET /groups/:id/service_accounts/:user_id/personal_access_tokens` endpoint returns an HTTP 500 with `ArgumentError: wrong number of arguments (given 1, expected 0)` when the service account user has admin privileges. This affects any caller (admin or group owner) and is not specific to the caller's role.
This blocks the Terraform GitLab provider v18.8+ on self-managed instances where service accounts have admin privileges, as the provider switched to this endpoint in [terraform-provider-gitlab commit 9253647d](https://gitlab.com/gitlab-org/terraform-provider-gitlab/-/commit/9253647d).
#### Root Cause
`PersonalAccessTokensHelpers` defines a [`user(user_id)` method](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/lib/api/helpers/personal_access_tokens_helpers.rb#L59-L61) expecting one argument:
```ruby
def user(user_id)
UserFinder.new(user_id).find_by_id
end
```
`GroupServiceAccounts` [overrides this](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/ee/lib/api/group_service_accounts.rb#L17) as a zero-argument method:
```ruby
def user
user_group.provisioned_users.find_by_id(params[:user_id])
end
```
The endpoint calls [`finder_params(user)`](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/ee/lib/api/group_service_accounts.rb#L189) where `user` is the **service account** object. Inside `finder_params`, `can_admin_all_resources?` is checked on this service account object.
When the service account is **not** an admin, `finder_params` takes the [`else` branch](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/lib/api/helpers/personal_access_tokens_helpers.rb#L53) which never calls `user(...)` with an argument, so it works fine.
When the service account **is** an admin, `finder_params` enters the [admin branch](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/lib/api/helpers/personal_access_tokens_helpers.rb#L42) and calls `user(params[:user_id])` with one argument. Ruby dispatches this to the overridden zero-arg method, causing the `ArgumentError`.
#### Missing test coverage
The [spec file](https://gitlab.com/gitlab-org/gitlab/-/blob/v18.9.3-ee/ee/spec/requests/api/group_service_accounts_spec.rb#L630-L644) for this endpoint does not test the case where the service account has admin privileges. It only covers `when the user is a top-level-group owner` and `when user is not a top-level-group owner`. Other endpoints in the same file (DELETE, rotate) include admin test contexts, but this GET endpoint does not.
#### Introduced by
!186105 ("Add GET /personal_access_tokens to service_accounts API"), merged 2025-04-01, milestone 17.11.
#### Affected versions
17.11 and later (all versions since the endpoint was added). Confirmed still present in 18.10.1.
#### Steps to reproduce
1. Open a `gitlab-rails console` and create the test data:
```ruby
# Create a group owner
owner_result = ::Users::CreateService.new(
User.find(1),
{
name: 'Test Owner',
username: 'sa-bug-test-owner',
email: 'sa-bug-test-owner@example.com',
password: SecureRandom.hex(16) + 'A1!',
skip_confirmation: true,
organization_id: Organizations::Organization.default_organization.id
}
).execute
owner = owner_result.payload[:user]
# Create group
result = Groups::CreateService.new(
owner,
{ name: 'sa-bug-test', path: 'sa-bug-test', organization_id: Organizations::Organization.default_organization.id }
).execute
group = result.payload[:group]
# Create service account (requires instance admin)
sa_result = ::Namespaces::ServiceAccounts::CreateService.new(
User.find(1),
{ namespace_id: group.id, organization_id: group.organization_id }
).execute
sa = sa_result.payload[:user]
# Make the service account an admin (this is the trigger condition)
sa.admin = true
sa.save!
# Create a PAT for the service account
token = PersonalAccessToken.create!(
user: sa,
name: 'test-token',
scopes: ['api'],
expires_at: 1.day.from_now
)
# Create an owner PAT
owner_pat = PersonalAccessToken.create!(
user: owner,
name: 'owner-test-token',
scopes: ['api'],
expires_at: 1.day.from_now
)
puts "Group ID: #{group.id}"
puts "Service Account ID: #{sa.id}"
puts "Owner PAT: #{owner_pat.token}"
```
2. Call the endpoint as the group owner:
```shell
curl --header "PRIVATE-TOKEN: <owner_pat_token>" \
"http://<gitlab-host>/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens"
```
3. Observe HTTP 500 response.
4. Verify the bug is specific to admin service accounts by removing admin status:
```ruby
sa = User.find(<sa_id>)
sa.admin = false
sa.save!
```
5. Repeat the same curl request.
6. Observe HTTP 200 response with the token list.
#### Expected behavior
The endpoint should return a 200 with the list of personal access tokens regardless of whether the service account has admin privileges.
#### Actual behavior
When the service account has admin privileges, the endpoint returns HTTP 500:
```shell
$ curl --header "PRIVATE-TOKEN: <OWNER_PAT>" \
"https://gitlab.example.com/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens"
{"message":"500 Internal Server Error"}
```
After removing admin status from the service account, the same request returns HTTP 200:
```shell
$ curl --header "PRIVATE-TOKEN: <OWNER_PAT>" \
"https://gitlab.example.com/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens"
[{"id":24,"name":"test-token","revoked":false,"created_at":"2026-04-15T05:20:27.451Z","description":null,"scopes":["api"],"user_id":87,"last_used_at":null,"active":true,"expires_at":"2026-04-16"}]
```
Relevant `api_json.log` entry:
```json
{
"status": 500,
"method": "GET",
"path": "/api/v4/groups/<group_id>/service_accounts/<sa_id>/personal_access_tokens",
"route": "/api/:version/groups/:id/service_accounts/:user_id/personal_access_tokens",
"username": "sa-bug-test-owner",
"token_type": "PersonalAccessToken",
"exception.class": "ArgumentError",
"exception.message": "wrong number of arguments (given 1, expected 0)",
"exception.backtrace": [
"ee/lib/api/group_service_accounts.rb:17:in `user'",
"lib/api/helpers/personal_access_tokens_helpers.rb:42:in `finder_params'",
"ee/lib/api/group_service_accounts.rb:192:in `block (4 levels) in <class:GroupServiceAccounts>'"
],
"meta.feature_category": "user_management"
}
```
#### Workaround
Pin the Terraform GitLab provider to v18.7.x or earlier, which uses `GET /api/v4/personal_access_tokens/:id` instead of the affected group-scoped endpoint.
issue